Rest的使用和原理
Rest風格支持(使用HTTP請求方式動詞來表示對資源的操作)
• 以前:/getUser 獲取用戶 /deleteUser 刪除用戶 /editUser 修改用戶 /saveUser 保存用戶
• 現在: /user GET-獲取用戶 DELETE-刪除用戶 PUT-修改用戶 POST-保存用戶。
看下面的一個例子,這是一個表單:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>hah</title>
</head>
<body>
<form action="/user" method="get">
<input value="get" type="submit">
</form>
<form action="/user" method="post">
<input value="post" type="submit">
</form>
<form action="/user" method="post">
<input value="put" type="submit">
</form>
<form action="/user" method="post">
<input value="delete" type="submit">
</form>
</body>
</html>
處理上述表單提交的controller。但是我們以put方式和delete方式提交表單時,發現返回的都是GET-張三,不支持put和delete請求。
@RestController
public class UserController {
@RequestMapping(value = "/user",method = RequestMethod.GET)
public String getUser(){
return "GET-張三";
}
@RequestMapping(value = "/user",method = RequestMethod.POST)
public String saveUser(){
return "POST-張三";
}
@RequestMapping(value = "/user",method = RequestMethod.PUT)
public String putUser(){
return "PUT-張三";
}
@RequestMapping(value = "/user",method = RequestMethod.DELETE)
public String deleteUser(){
return "DELETE-張三";
}
}
在WebMvcAutoConfiguration中hiddenHttpMethodFilter方法,返回一個filter->OrderedHiddenHttpMethodFilter。OrderedHiddenHttpMethodFilter繼承自HiddenHttpMethodFilter。@ConditionalOnMissingBean({HiddenHttpMethodFilter.class})需要之前HiddenHttpMethodFilter沒有被注入,並且 @ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = {"enabled"})要求spring.mvc.hiddenmethod.filter.enabled=true.
@Bean
@ConditionalOnMissingBean({HiddenHttpMethodFilter.class})
@ConditionalOnProperty(
prefix = "spring.mvc.hiddenmethod.filter",
name = {"enabled"}
)
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
return new OrderedHiddenHttpMethodFilter();
}
public class OrderedHiddenHttpMethodFilter extends HiddenHttpMethodFilter implements OrderedFilter {
/**
* The default order is high to ensure the filter is applied before Spring Security.
*/
public static final int DEFAULT_ORDER = REQUEST_WRAPPER_FILTER_MAX_ORDER - 10000;
private int order = DEFAULT_ORDER;
@Override
public int getOrder() {
return this.order;
}
public void setOrder(int order) {
this.order = order;
}
}
HiddenHttpMethodFilter中有個DEFAULT_METHOD_PARAM字段"_method"。所以我們在提交表單時也要提交一個"_method"參數。並且spring.mvc.hiddenmethod.filter.enabled=true.
public class HiddenHttpMethodFilter extends OncePerRequestFilter {
private static final List<String> ALLOWED_METHODS =
Collections.unmodifiableList(Arrays.asList(HttpMethod.PUT.name(),
HttpMethod.DELETE.name(), HttpMethod.PATCH.name()));
public static final String DEFAULT_METHOD_PARAM = "_method";
private String methodParam = DEFAULT_METHOD_PARAM;
public void setMethodParam(String methodParam) {
Assert.hasText(methodParam, "'methodParam' must not be empty");
this.methodParam = methodParam;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
HttpServletRequest requestToUse = request;
if ("POST".equals(request.getMethod()) && request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) == null) {
String paramValue = request.getParameter(this.methodParam);
if (StringUtils.hasLength(paramValue)) {
String method = paramValue.toUpperCase(Locale.ENGLISH);
if (ALLOWED_METHODS.contains(method)) {
requestToUse = new HttpMethodRequestWrapper(request, method);
}
}
}
filterChain.doFilter(requestToUse, response);
}
private static class HttpMethodRequestWrapper extends HttpServletRequestWrapper {
private final String method;
public HttpMethodRequestWrapper(HttpServletRequest request, String method) {
super(request);
this.method = method;
}
@Override
public String getMethod() {
return this.method;
}
}
}
因此修改表單如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>hah</title>
</head>
<body>
<form action="/user" method="get">
<input value="get" type="submit">
</form>
<form action="/user" method="post">
<input value="post" type="submit">
</form>
<form action="/user" method="post">
<input name="_method" type="hidden" value="put">
<input value="put" type="submit">
</form>
<form action="/user" method="post">
<input name="_method" type="hidden" value="delete">
<input value="delete" type="submit">
</form>
</body>
</html>
配置yaml:
spring:
mvc:
hiddenmethod:
filter:
enabled: true #開啟頁面表單的Rest功能
修改表單,配置yaml之后,再點擊delete 和put就可以發送delete 和put請求了。
Rest原理
表單提交要使用REST的時候:
-
表單提交會帶上_method=PUT
-
請求過來被HiddenHttpMethodFilter攔截
-
1.如果請求正常,並且是POST(if ("POST".equals(request.getMethod()) && request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) == null) )則獲取_method的值
-
- 2.獲取到_method的值,如果是ALLOWED_METHODS里的方法(PUT.DELETE.PATCH)則將方法傳給包裝模式類requesWrapper,requesWrapper只改變了請求方式。
原生request(post),包裝模式requesWrapper重寫了getMethod方法,返回的是傳入的值。
過濾器鏈放行的時候用wrapper。以后的方法調用getMethod是調用requesWrapper的。
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
HttpServletRequest requestToUse = request;
if ("POST".equals(request.getMethod()) && request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) == null) {
// paramValue拿到的是_method
String paramValue = request.getParameter(this.methodParam);
if (StringUtils.hasLength(paramValue)) {
String method = paramValue.toUpperCase(Locale.ENGLISH);
if (ALLOWED_METHODS.contains(method)) {
requestToUse = new HttpMethodRequestWrapper(request, method);
}
}
}
filterChain.doFilter(requestToUse, response);
}
private static final List<String> ALLOWED_METHODS =
Collections.unmodifiableList(Arrays.asList(HttpMethod.PUT.name(),
HttpMethod.DELETE.name(), HttpMethod.PATCH.name()));
requesWrapper:
private static class HttpMethodRequestWrapper extends HttpServletRequestWrapper {
private final String method;
public HttpMethodRequestWrapper(HttpServletRequest request, String method) {
super(request);
this.method = method;
}
@Override
public String getMethod() {
return this.method;
}
}
將_method改成其他名字
將_method改成了_m,此時因為程序中無法獲取_m,所以我們表單中點擊delete依然是post,因此我們需要再更改表單即可。
//自定義filter
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.HiddenHttpMethodFilter;
@Configuration(proxyBeanMethods = false)
public class RestConfig {
@Bean
public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
HiddenHttpMethodFilter methodFilter = new HiddenHttpMethodFilter();
methodFilter.setMethodParam("_m");
return methodFilter;
}
}
修改form表單,添加_m:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>hah</title>
</head>
<body>
<form action="/user" method="get">
<input value="get" type="submit">
</form>
<form action="/user" method="post">
<input value="post" type="submit">
</form>
<form action="/user" method="post">
<input name="_method" type="hidden" value="put">
<input name="_m" type="hidden" value="put">
<input value="put" type="submit">
</form>
<form action="/user" method="post">
<input name="_method" type="hidden" value="delete">
<input name="_m" type="hidden" value="delete">
<input value="delete" type="submit">
</form>
</body>
</html>
Rest使用客戶端工具
- 如PostMan直接發送Put、delete等方式請求,無需Filter。表單只能用get,post請求。
請求處理----參數注解
@PathVariable、@RequestHeader、@ModelAttribute、@RequestParam、@MatrixVariable、@CookieValue、@RequestBody
參數注解作用
@PathVariable
作用:@PathVariable
是spring3.0的一個新功能:接收請求路徑中占位符的值,將URL中占位符參數{xxx}
綁定到處理器類的方法形參中@PathVariable(“xxx“)
,上如果有多個占位符,形參列表需要定義多個參數,不是很方便,可以直接定義一個map集合 @PathVariable Map<String,String> kv
,會自動映射多個參數;
@RequestParam
@RequestParam主要用於將請求參數區域的數據映射到控制層方法的參數上
@RequestParam(value=”參數名”,required=”true/false”,defaultValue=””)
value
:請求中傳入參數的名稱,即前端傳過來時定義的參數名。如果不設置value值,則前端定義的參數名必須和后端接口參數名相同required
:該參數是否為必傳項。默認是true,表示請求中一定要傳入對應的參數,否則會報404錯誤;如果設置為false,當請求中沒有此參數,將會默認為null。而對於基本數據類型的變量,則必須有值,這時會拋出空指針異常。如果允許空值,則接口中變量需要使用包裝類來聲明。defaultValue
:參數的默認值,如果請求中沒有同名的參數時,該變量默認為此值。
@RequestBody
作用:@RequestBody主要用來接收前端傳遞給后端的json字符串中的數據的(請求體中的數據的)
- GET方式無請求體,所以使用@RequestBody接收數據時,前端不能使用GET方式提交數據,而是用POST方式進行提交
- 在后端的同一個接收方法里,@RequestBody與@RequestParam()可以同時使用,@RequestBody最多只能有一個,而@RequestParam()可以有多個
- 如果參數時放在請求體application/json傳入后台的話,那么后台要用@RequestBody才能接收到
- 如果不是放在請求體中的話,那么后台接收前台傳過來的參數時,要用@RequestParam來接收,或者形參前 什么也不寫也能接收
@CookieValue
同請求頭類似,可以獲取cookie屬性,也可以將獲取到的屬性封裝為cookie對象
@RequestAttribute
獲取請求域中的信息
@Controller
public class GoController {
@GetMapping("/goto")
public String goPage(HttpServletRequest request){
request.setAttribute("msg","成功");
request.setAttribute("code","200");
return "forward:/success";
}
@ResponseBody
@GetMapping("/success")
public Map<String,Object> getmap(@RequestAttribute("msg") String msg,
@RequestAttribute("code") String code){
HashMap<String, Object> map = new HashMap<>();
map.put("msg",msg);
map.put("code",code);
return map;
}
}
@MatrixVariable
讀取矩陣變量,矩陣變量語法如下,每一個變量之間用分號隔開
語法: 請求路徑:/cars/sell;low=34;brand=byd,audi,yd
//cars/sell;low=34;brand=byd,audi,yd
@GetMapping("/car/{path}")
public Map test2(@MatrixVariable("low") String low,
@MatrixVariable("brand") List<String> brand,
@PathVariable("path") String path){
HashMap<String, Object> map = new HashMap<>();
map.put("low",low);
map.put("brand",brand);
map.put("path",path);
return map;
}
//{"path":"sell","low":34,"brand":["byd","audi","yd"]}
示例:
請求地址如果為:http://localhost:9999/car/2/owner/zhangsan?age=18&inters=basketball&inters=football
@PathVariable("id") Integer id: 獲取路徑中{id}
@PathVariable("username") String name: 獲取路徑中{username}
@PathVariable Map<String, String> pv: 獲取路徑中所有的路徑變量
@RequestHeader("User-Agent") String userAgent: 獲取請求頭中userAgent
@RequestHeader Map<String, String> header: 獲取請求頭中所有參數
@RequestParam("age") Integer age: 獲取請求參數中"age"
@RequestParam("inters") List<String> inters: 獲取請求參數中"inters"
@RequestParam Map<String, String> params: 獲取所有的請求參數
@CookieValue("_ga") String _ga: 獲取cookie中_ga的值
@CookieValue("_ga") Cookie cookie) : 獲取cookie
import javax.servlet.http.Cookie;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RestController
public class ParameterTestController {
// http://localhost:9999/car/2/owner/zhangsan?age=18&inters=basketball&inters=football
@GetMapping("/car/{id}/owner/{username}")
public Map<String, Object> getCar(@PathVariable("id") Integer id,
@PathVariable("username") String name,
@PathVariable Map<String, String> pv,
@RequestHeader("User-Agent") String userAgent,
@RequestHeader Map<String, String> header,
@RequestParam("age") Integer age,
@RequestParam("inters") List<String> inters,
@RequestParam Map<String, String> params,
@CookieValue("_ga") String _ga,
@CookieValue("_ga") Cookie cookie) {
Map<String, Object> map = new HashMap<>();
map.put("id", id);
map.put("name", name);
map.put("pv", pv);
map.put("userAgent", userAgent);
map.put("headers", header);
map.put("age", age);
map.put("inters", inters);
map.put("params", params);
map.put("_ga", _ga);
System.out.println(cookie.getName() + "===>" + cookie.getValue());
return map;
}
@PostMapping("/save")
public Map postMethod(@RequestBody String content) {
Map<String, Object> map = new HashMap<>();
map.put("content", content);
return map;
}
//1、語法: 請求路徑:/cars/sell;low=34;brand=byd,audi,yd
//2、SpringBoot默認是禁用了矩陣變量的功能
// 手動開啟:原理。對於路徑的處理。UrlPathHelper進行解析。
// removeSemicolonContent(移除分號內容)支持矩陣變量的
//3、矩陣變量必須有url路徑變量才能被解析
@GetMapping("/cars/{path}")
public Map carsSell(@MatrixVariable("low") Integer low,
@MatrixVariable("brand") List<String> brand,
@PathVariable("path") String path) {
Map<String, Object> map = new HashMap<>();
map.put("low", low);
map.put("brand", brand);
map.put("path", path);
return map;
}
// /boss/1;age=20/2;age=10
@GetMapping("/boss/{bossId}/{empId}")
public Map boss(@MatrixVariable(value = "age", pathVar = "bossId") Integer bossAge,
@MatrixVariable(value = "age", pathVar = "empId") Integer empAge) {
Map<String, Object> map = new HashMap<>();
map.put("bossAge", bossAge);
map.put("empAge", empAge);
return map;
}
}