文章目錄
- 一.前言
- 1.Rest風格的請求
- 2.表單如何發出delete和put請求
- 3.完整代碼示例:
- 二.原始碼分析
- 1.HiddenHttpMethodFilter類中的doFilterInternal方法
- 2.一步步分析原始碼:
- 1.第一行代碼
- 2.第二行代碼
- 3.第三行代碼
- 4.第四行代碼
- 5.第五行代碼
- 6.第六行代碼
- 7.第七行代碼
- 8.第八行代碼
一.前言
1.Rest風格的請求
我們現在一般喜歡用Rest風格的請求,即使用HTTP請求方式動詞來表示對資源的操作,
舉個例子:
- 比如我們以前對學生資訊進行相關的增刪改查操作,定義的url路徑可能是這樣的:
/getStudent獲取學生資訊/deleteStudent洗掉學生資訊/editStudent修改學生資訊/savaStudent保存學生資訊
如果這個專案比較大,我們的路徑起名字都感覺很麻煩,
- 現在我們用Rest風格這樣做:
- 所有對學生的操作都叫
/Student,如何表示對學生的增刪改查呢,使用HTTP請求方式動詞來表示對資源的操作, GET方式請求表示獲取學生資訊DELETE方式請求表示洗掉學生資訊PUT方式請求表示修改學生資訊POST方式請求表示保存學生資訊
利用這些不同的請求方式動詞來區分不同的請求,
- 所有對學生的操作都叫
2.表單如何發出delete和put請求
我們以前用SpringMVC來完成這些事情,我們需要配置一個叫HiddenHttpMethodFilter的Filter;
我們來到WebMvcAutoConfiguration中,可以看到它已經配置了一個HiddenHttpMethodFilter,如下:

也就是說默認我們的rest功能是可以用的,但是它通過@ConditionalOnProperty設定配置屬性前綴spring.mvc.hiddenmethod.filter的配置屬性名字enabled默認為false,來默認該功能是不開啟的,我們在組態檔中手動開啟它:
spring:
mvc:
hiddenmethod:
filter:
enabled: true #選擇性開啟
為什么要配置HiddenHttpMethodFilter呢,因為我們的表單提交的請求方式中只支持GET 和POST方式的請求,它不支持DELETE和 PUT 方式的請求,如下:
我們的表單的method只有兩個選項,get和post:

那我們如何用表單發出delete和put請求呢? 我們可以這樣做(為什么這樣做我們之后在原始碼分析中會講到),method還是等于post請求方式,但是添加了一個name="_method"、value="DELETE"或者value="put"的input標簽(value中的delete或者put大小寫都可以,之后我們在講解原始碼的程序中會看到SpringBoot會自動把其值全部變成大寫),并且我們把這個input標簽隱藏起來,如下:

這里先說一下為什么name="_method",這已經在HiddenHttpMethodFilter類中寫好了,如下:

我們也可以呼叫setMethodParam設定它的值,如下:

自己新建一個配置類,用@Bean注入物件到容器中,在該方法回傳物件前,呼叫setMethodParam來設定methodParam(也就是name)的值,如下:(這里只是做一個演示,下文的所有代碼中沒有該配置類)

3.完整代碼示例:
Controller如下:
@RestController
public class HelloController {
// @RequestMapping(value = "/student",method = RequestMethod.GET)
@GetMapping("/student")
public String getUser(){
return "`GET` 方式請求表示獲取學生資訊";
}
// @RequestMapping(path = "/student",method = RequestMethod.POST)
@PostMapping("/student")
public String saveUser(){
return "`POST` 方式請求表示保存學生資訊";
}
// @RequestMapping(value = "/student",method = RequestMethod.PUT)
@PutMapping("/student")
public String putUser(){
return "`PUT` 方式請求表示修改學生資訊";
}
// @RequestMapping(value = "/student",method = RequestMethod.DELETE)
@DeleteMapping("/student")
public String deleteUser(){
return "`DELETE` 方式請求表示洗掉學生資訊";
}
}
index.html:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<h1>SpringBoot Web ,歡迎您!</h1>
測驗REST風格:
<!--method為空默認為get方式請求-->
<form action="/student" method="">
<input value="GET請求" type="submit">
</form>
<form action="/student" method="post">
<input value="POST請求" type="submit">
</form>
<form action="/student" method="post">
<input name="_method" value="DELETE" type="hidden">
<input value="DELETE請求" type="submit">
</form>
<form action="/student" method="post">
<input name="_method" value="put" type="hidden">
<input value="PUT請求" type="submit">
</form>
<hr>
</body>
</html>
歡迎頁面:

點擊測驗get請求:

點擊測驗post請求:

點擊測驗delete請求:

點擊測驗put請求:

可以看到這四種方式的請求我們都可以獲取并訪問到,那么底層原始碼是怎么寫的呢,接下來我將具體講解原始碼所做的一些事情,
二.原始碼分析
1.HiddenHttpMethodFilter類中的doFilterInternal方法
上述提到的hiddenHttpMethodFilter()方法中回傳了OrderedHiddenHttpMethodFilter類的物件,把OrderedHiddenHttpMethodFilter物件注冊到容器中:

而OrderedHiddenHttpMethodFilter類有繼承了HiddenHttpMethodFilter類:

現在我們只需要來看HiddenHttpMethodFilter類了,而HiddenHttpMethodFilter類中的doFilterInternal方法是處理上述程序的主要方法體,我們給doFilterInternal方法打上斷點,如下:

2.一步步分析原始碼:
啟動debug來除錯程式,接下來我們來一步步分析:
首先我們訪問我們的歡迎頁面:
點擊put請求,表單提交會帶上name = value的資訊, 在我們的put表單中,name="_method" value=“put”,如下:

所以表單提交時會帶上_method=put的資訊,
1.第一行代碼
表單提交后請求會被HiddenHttpMethodFilter攔截,來到了doFilterInternal方法中,首先拿到我們的原生請求request,把它賦值給一個區域變數requestToUse,如下:
第一行代碼:

2.第二行代碼
然后進入if條件判斷中,判斷我們的原生request請求方式是不是POST方式(也就是我們form表單的method屬性是不是post方式)和判斷當前請求是否沒有錯誤,如下:
剛才我們寫的form表單:

第二行代碼:

因為我們點擊的是put,它的method屬性是post,然后也沒有什么錯誤,我們滿足條件,進入if陳述句體中,
3.第三行代碼
然后呢,接下來它呼叫request.getParameter方法獲取請求引數并把它賦值給區域變數paramValue:
第三行代碼:

其中request.getParameter實參的this.methodParam是什么呢?我們點進去看一下,如下:

它就是_method字符,這也就解釋了為什么之前我們要在form表單中再添加一個input標簽,其name值為_method,request.getParameter(_method)就或許到了其value值,因為我們剛才點擊put請求,表單提交會帶上name = value的資訊, 在我們的put表單中,name="_method" value=“put”,所以區域變數paramValue得到的值為value的值,為put,
4.第四行代碼
接著往下走,我們來到第四行代碼,這里又是一個if陳述句判斷,StringUtils.hasLength(paramValue)方法它判斷的是字串是否為null或者為為空,顯然我們的paramValue不為null也不為空,為put,滿足if判斷條件,
StringUtils.hasLength方法:

第四行代碼:

5.第五行代碼
接著往下走,我們的第五行代碼是把我們的paramValue區域變數變成大寫,也就是把put變成大寫的PUT,然后賦值給新的區域變數method,如下:
第五行代碼:

6.第六行代碼
又到了if陳述句判斷的地方,這里判斷我們的區域變數method(也就是PUT)是否包含在ALLOWED_METHODS這個List集合中,為什么ALLOWED_METHODS是一個集合呢?我們點進去看一下就知道了:

它是這么一個集合,它里面有PUT、DELETE、和PATCH,它們都是列舉型別,我們分別點進去看一下:

它們的name方法是得到其名稱,也就是同名的字串,也就得到了PUT,DELETE和PATCH字串,
我們的區域變數method(也就是PUT)在其中,if陳述句為true,
第六行代碼:

7.第七行代碼
繼續往下走,接下來遇到了一個new HttpMethodRequestWrapper新建了一個物件來取代原來的原生request,我們點進這個HttpMethodRequestWrapper中看一下:

我們發現這個HttpMethodRequestWrapper繼承了HttpServletRequestWrapper,我們點進HttpServletRequestWrapper看一下:

發現這個HttpServletRequestWrapper最侄訓是實作了HttpServletRequest,所以呢HttpServletRequestWrapper的子類HttpMethodRequestWrapper他還是一個HttpServletRequest請求,
現在我們回到HttpMethodRequestWrapper類中,具體看其類內部是怎么回事:
我們呼叫了HttpMethodRequestWrapper的構造器,傳遞了兩個引數分別是原生的request請求和method(也就是PUT)請求方式,在構造器中我們把請求方式method賦值給類的屬性method,如下:

然后重寫了父類的getMethod方法,回傳我們剛剛賦值的method(也就是PUT ),
呼叫了構造器之后把其創建的物件賦值給了區域變數requestToUse,當我們呼叫requestToUse的getMethod方法時,我們回傳的就是PUT了,這個地方使用了一個包裝設計模式,很巧妙,
第七行代碼:

8.第八行代碼
呼叫filter過濾器filterchain的dofilter方法,將把自身接收到的請求request物件和response物件和自身物件即filterchain作為下一個過濾器的dofilter的形參傳遞過去,這樣才能使得過濾器傳遞下去,但是這里的request物件是requestToUse,也就是我們剛剛重寫了getMethod方法的requestToUse,當呼叫getMethod的方法時回傳的是PUT,
第八行代碼:

dubug放行后運行結果:

上述我們都說的是表單提交的方式發送請求,
如果是客戶端直接發送請求,我們的請求方式可以直接是PUT、DELETE等方式,如果不是POST請求它會只運行第一行代碼和第八行代碼,
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/249787.html
標籤:其他
上一篇:MyBatis-Plus快速入門-(干貨滿滿+超詳細)
下一篇:關于one-hot編碼
