Spring Boot核心技術之Rest對映以及原始碼的分析
該部落格主要是Rest對映以及原始碼的分析,主要是思路的學習。SpringBoot版本:2.4.9
環境的搭建
主要分兩部分:
Index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/user" method="get">
<input value="REST-GET提交" type="submit" />
</form>
<form action="/user" method="post">
<input value="REST-POST提交" type="submit" />
</form>
<form action="/user" method="delete">
<input value="REST-DELETE 提交" type="submit"/>
</form>
<form action="/user" method="put">
<input value="REST-PUT提交"type="submit" />
</form>
</body>
</html>
controller
package com.xbhong.Controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
@RestController
public class myContro {
@RequestMapping(value = "/user",method = RequestMethod.GET)
public String getUser(){
return "GET-張三";
}
// @PostMapping("/user")
@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-張三";
}
}
測試功能是否可用:
觀察效果:
可以看到最後兩個請求都變成Get的請求了。由此我們可以引出來如下問題:
- 為什麼不同的請求會出現相同的結果
- 怎麼實現的才能完成我們的功能
後面的文章就是圍繞上述的問題進行展開的。
解決問題:
像之前的SpringMVC中的表單請求通過HiddenHttpMethodFilter實現的,這樣我們查一下在SpringBoot中是怎麼樣的。
預設雙擊Shift鍵,輸入WebMvcAutoConfiguration這個類
找到預設配置的過濾器:
@Bean
@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
@ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled", matchIfMissing = false)
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
return new OrderedHiddenHttpMethodFilter();
}
通過OrderedHiddenHttpMethodFilter進入父類HiddenHttpMethodFilter:
在使用之前,我們需要將後兩個的請求方式改寫成post方式,並且需要在請求的時候傳入一個_method
方法(設定隱藏域);
<form action="/user" method="post">
<input name="_method" type="hidden" value="DELETE"/>
<input value="REST-DELETE 提交" type="submit"/>
</form>
<form action="/user" method="post">
<input name="_method" type="hidden" value="PUT" />
<input value="REST-PUT提交"type="submit" />
</form>
為什麼要這樣設定呢?我們到HiddenHttpMethodFilter中看下。
流程:
- 第一步儲存了傳入的請求
- 當該請求時post,並且請求沒有異常,才能進入下面方法,不是Post請求將直接通過過濾器鏈放行。
- 獲取請求中的引數(this.methodParam)
- DEFAULT_METHOD_PARAM = _method獲得(作為真正的請求方式)
然後再測試,發現還是沒有實現。
我再回到WebMvcConfiguration中:
@Bean
@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
@ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled", matchIfMissing = false)
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
return new OrderedHiddenHttpMethodFilter();
}
首先該類存在於容器中。
@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
當容器中不存在HiddenHttpMethodFilter
這個類的時候,下面內容開啟(條件裝配);
@ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled", matchIfMissing = false)
表示:繫結的配置檔案中:spring.mvc.hiddenmethod.filter
名字為enable是預設不開啟的(後續版本可能開啟)。這樣我們就找到了問題的所在。
所以我們需要在配置檔案中配置(兩種方法都可以)。
yaml:
spring:
mvc:
hiddenmethod:
filter:
enabled: true
properties:
spring.mvc.hiddenmethod.filter.enabled=true
重啟專案:成功解決。
原始碼分析:
主要是分析doFilterInternal:
@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);
}
-
表單提交會帶上_method=PUT
-
請求過來被HiddenHttpMethodFilter攔截
-
請求是否正常,並且是POST
-
獲取到_method的值。
-
相容以下請求;PUT.DELETE.PATCH
當方法走到上述程式碼11行時,進入ALLOWED_METHODS:
private static final List<String> ALLOWED_METHODS = Collections.unmodifiableList(Arrays.asList(HttpMethod.PUT.name(), HttpMethod.DELETE.name(), HttpMethod.PATCH.name()));
如果請求裡的引數在ALLOWED_METHODS存在,則執行下面程式碼。
-
原生request(post),包裝模式requesWrapper重寫了getMethod方法,返回的是傳入的值。
進入HttpMethodRequestWrapper物件中,向上找父類。本質還是HttpServletRequest
由下面程式碼:
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; } }
可知,接收到前面的請求封裝為HttpMethodRequestWrapper返回。
-
過濾器鏈放行的時候用wrapper。以後的方法呼叫getMethod是呼叫requesWrapper的。
部分補充:
由於原始碼中規則:將獲得請求中的引數無條件的以英文格式轉完成大寫,所以前端的value="PUT"
value的值大小寫無影響。
String paramValue = request.getParameter(this.methodParam);
if (StringUtils.hasLength(paramValue)) {
String method = paramValue.toUpperCase(Locale.ENGLISH);
Rest使用客戶端工具,
- 如PostMan直接傳送Put、delete等方式請求,無需Filter。
參考文獻:
結束:
如果你看到這裡或者正好對你有所幫助,希望能點個關注或者推薦,感謝;
有錯誤的地方,歡迎在評論指出,作者看到會進行修改。