幾種注解參數綁定區別:https://www.cnblogs.com/guoyinli/p/7056146.html
@RequestBody的使用:https://blog.csdn.net/justry_deng/article/details/80972817
全面解析@ModelArrribute:https://www.cnblogs.com/cobcmw/p/12092591.html
Content-Type屬性指定請求和響應的HTTP內容類型。如果未指定ContentType,默認響應的內容類型為 text/html,默認請求的內容類型為 application/x-www-form-urlencoded。Content-Type一般只存在於Post方法中,因為Get方法是不含“body”的,它的請求參數都會被編碼到url后面,所以在Get方法中加Content-Type是無效的。
application/x-www-form-urlencoded
前面幾個為html、純文本、css、javascript的文件類型。后面四個則是POST的發包方式。
application/x-www-form-urlencoded 是常見的表單發包方式,普通的表單提交,或者js發包,默認都是這種方式,數據被編碼為key/value格式發送到服務器。
multipart/form-data 用在發送文件的POST包。multipart/form-data的請求頭必須包含一個特殊的頭信息:content-type,且其值也必須規定為multipart/form-data,同時還需要規定一個內容分隔符即boundary用於分割請求體中的多個POST的內容,如文件內容和文本內容自然需要分割開來,不然接收方法就無法正常解析和還原這個文件了(關於boundary可參加這里)。
application/json HTTP通信中並不存在所謂的json,而是將string轉成json罷了,所以application/json也可以理解成text/plain。
package com.example.spring.modular; import java.util.List; public class User { private String id; private List<String> friends; public String getId() { return id; } public void setId(String id) { this.id = id; } public List<String> getFriends() { return friends; } public void setFriends(List<String> friends) { this.friends = friends; } }
然后創建如下controller,先來看一下spring默認的匹配策略。
package com.example.spring.modular; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @Controller public class ReceiveParamController { @RequestMapping("/def") public String def0(String id) { System.out.println(id); // 斷點位置0 return "hello.html"; // 改為"/hello.html" } @RequestMapping("/default") public String def1(String id) { System.out.println(id); // 斷點位置1 return "hello.html"; // 改為"/hello.html" } @RequestMapping("/default/{id}") public String def2(String id) { System.out.println(id); // 斷點位置2 return "hello.html"; // 改為"/hello.html" } @RequestMapping("/def3") public String def3(Map<String, String> map) { System.out.println(map); // 斷點位置def3 return "/hello.html"; } @RequestMapping("/def4") public String def4(List<String> list) { System.out.println(list); // 斷點位置def4 return "/hello.html"; } @RequestMapping("/def5") public String def5(User user) { System.out.println(user); // 斷點位置def5 return "/hello.html"; } }
瀏覽器地址輸入:http://localhost:8080/de 、 http://localhost:8080/defa
瀏覽器地址輸入:http://localhost:8080/default
瀏覽器地址輸入:http://localhost:8080/default/
后端先進入斷點位置1然后進入斷點位置2,然后在進入斷點位置2,最終結果報錯:循環視圖路徑。【id參數始終為null】
為了解決這個問題,我們在return的html之前加上一個/,表示轉發的路由以跟路徑開頭,這樣就不會出現循環視圖的問題了。
改為"/hello.html"后,后端只進入斷點位置1。【id參數為null】
瀏覽器地址輸入:http://localhost:8080/default?id=1
瀏覽器地址輸入:http://localhost:8080/default/?id=1
后端先進入斷點位置1然后進入斷點位置2,然后在進入斷點位置2,最終結果報錯:循環視圖路徑。【id參數始終為"1"】
改為"/hello.html"后,后端只進入斷點位置1。【id參數為1】
瀏覽器地址輸入:http://localhost:8080/default/3?id=1
后端先進入斷點位置2,然后在進入斷點位置2,最終結果報錯:循環視圖路徑。【id參數始終為"1"】
改為"/hello.html"后,后端只進入斷點位置2。【id參數為1】
瀏覽器地址輸入:http://localhost:8080/default/3/?id=1
后端只進入一次斷點位置2,然后結果報錯:循環視圖路徑。【id參數為"1"】
改為"/hello.html"后,后端只進入斷點位置2。【id參數為1】
使用postman,選擇body-raw-JSON,輸入:{"id":"1"},send,進入斷點位置def3,但是map里參數size為0,沒有收到參數。除此之外,在postman中無論使用Params的Query Params方式,還是使用Body下的每一種方式,都無法接收到參數。
curl --location --request GET 'http://localhost:8080/def3' \ --header 'Content-Type: application/json' \ --data-raw '{ "id":"999" }'
無論使用postman的那種方式,參數填入id/8,id/9,發送請求都不能進入斷點def4位置。說明不可映射成list
使用postman,發送get請求http://localhost:8080/def5,當選擇Params下的Query Params和Body下from-data,參數key/value填入id/999,firends/a,friends/b時能成功映射user的id為999,friends為一個不為null的list集合,其他情況如:Body下x-www-form-urlencoded和Body下raw,JSON下{"id":"999","friends":["a","b"]},雖然也能進入方法,但是映射的user.id和friends都為null。
由測試0和測試1可得,spring默認的路由匹配為精准匹配,然后才是最長匹配。
為了更好的對比測試1和測試2,我們在application.yml增加如下,讓spring打印更詳細的日志信息:
# 表明靜態資源的位置 spring: resources: static-locations: classpath:/templates/ # 新增更詳細的日志信息 logging: level: root: info org.springframework.web: trace
@RequestMapping("/default") public String def1(String id) { System.out.println(id); return "hello.html"; // 沒改之前 } @RequestMapping("/default/") public String def2(String id) { System.out.println(id); return "hello.html"; // 沒改之前 }
依次訪問http://localhost:8080/default和http://localhost:8080/default/打印日志信息分別如下:
2020-09-08 21:49:49.321 TRACE 14288 --- [nio-8080-exec-5] o.s.web.servlet.DispatcherServlet : GET "/default", parameters={}, headers={masked} in DispatcherServlet 'dispatcherServlet' 2020-09-08 21:49:49.322 TRACE 14288 --- [nio-8080-exec-5] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to com.example.spring.modular.ReceiveParamController#def1(String) 2020-09-08 21:49:49.322 TRACE 14288 --- [nio-8080-exec-5] .w.s.m.m.a.ServletInvocableHandlerMethod : Arguments: [null] null 2020-09-08 21:49:49.323 TRACE 14288 --- [nio-8080-exec-5] o.s.w.s.v.InternalResourceViewResolver : View with key [hello.html] served from cache 2020-09-08 21:49:49.323 DEBUG 14288 --- [nio-8080-exec-5] o.s.w.s.v.ContentNegotiatingViewResolver : Selected 'text/html' given [text/html, application/xhtml+xml, image/webp, image/apng, application/signed-exchange;v=b3, application/xml;q=0.9, */*;q=0.8] 2020-09-08 21:49:49.323 TRACE 14288 --- [nio-8080-exec-5] o.s.web.servlet.DispatcherServlet : Rendering view [org.springframework.web.servlet.view.InternalResourceView: name 'hello.html'; URL [hello.html]] 2020-09-08 21:49:49.323 DEBUG 14288 --- [nio-8080-exec-5] o.s.w.servlet.view.InternalResourceView : View name 'hello.html', model {} 2020-09-08 21:49:49.323 DEBUG 14288 --- [nio-8080-exec-5] o.s.w.servlet.view.InternalResourceView : Forwarding to [hello.html] 2020-09-08 21:49:49.323 TRACE 14288 --- [nio-8080-exec-5] o.s.web.servlet.DispatcherServlet : "FORWARD" dispatch for GET "/hello.html", parameters={}, headers={masked} in DispatcherServlet 'dispatcherServlet' 2020-09-08 21:49:49.324 TRACE 14288 --- [nio-8080-exec-5] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped to HandlerExecutionChain with [ResourceHttpRequestHandler ["classpath:/templates/", "/"]] and 3 interceptors 2020-09-08 21:49:49.326 TRACE 14288 --- [nio-8080-exec-5] o.s.web.servlet.DispatcherServlet : No view rendering, null ModelAndView returned. 2020-09-08 21:49:49.326 DEBUG 14288 --- [nio-8080-exec-5] o.s.web.servlet.DispatcherServlet : Exiting from "FORWARD" dispatch, status 200, headers={masked} 2020-09-08 21:49:49.326 DEBUG 14288 --- [nio-8080-exec-5] o.s.web.servlet.DispatcherServlet : Completed 200 OK, headers={masked} ================================================================================================================================================================================================================================================= 2020-09-08 21:50:07.960 TRACE 14288 --- [nio-8080-exec-7] o.s.web.servlet.DispatcherServlet : GET "/default/", parameters={}, headers={masked} in DispatcherServlet 'dispatcherServlet' 2020-09-08 21:50:07.961 TRACE 14288 --- [nio-8080-exec-7] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to com.example.spring.modular.ReceiveParamController#def2(String) 2020-09-08 21:50:07.961 TRACE 14288 --- [nio-8080-exec-7] .w.s.m.m.a.ServletInvocableHandlerMethod : Arguments: [null] null 2020-09-08 21:50:07.962 TRACE 14288 --- [nio-8080-exec-7] o.s.w.s.v.InternalResourceViewResolver : View with key [hello.html] served from cache 2020-09-08 21:50:07.963 DEBUG 14288 --- [nio-8080-exec-7] o.s.w.s.v.ContentNegotiatingViewResolver : Selected 'text/html' given [text/html, application/xhtml+xml, image/webp, image/apng, application/signed-exchange;v=b3, application/xml;q=0.9, */*;q=0.8] 2020-09-08 21:50:07.963 TRACE 14288 --- [nio-8080-exec-7] o.s.web.servlet.DispatcherServlet : Rendering view [org.springframework.web.servlet.view.InternalResourceView: name 'hello.html'; URL [hello.html]] 2020-09-08 21:50:07.963 DEBUG 14288 --- [nio-8080-exec-7] o.s.w.servlet.view.InternalResourceView : View name 'hello.html', model {} 2020-09-08 21:50:07.963 DEBUG 14288 --- [nio-8080-exec-7] o.s.w.servlet.view.InternalResourceView : Forwarding to [hello.html] 2020-09-08 21:50:07.963 TRACE 14288 --- [nio-8080-exec-7] o.s.web.servlet.DispatcherServlet : "FORWARD" dispatch for GET "/default/hello.html", parameters={}, headers={masked} in DispatcherServlet 'dispatcherServlet' 2020-09-08 21:50:07.964 TRACE 14288 --- [nio-8080-exec-7] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped to HandlerExecutionChain with [ResourceHttpRequestHandler ["classpath:/templates/", "/"]] and 3 interceptors 2020-09-08 21:50:07.966 DEBUG 14288 --- [nio-8080-exec-7] o.s.w.s.r.ResourceHttpRequestHandler : Resource not found 2020-09-08 21:50:07.966 TRACE 14288 --- [nio-8080-exec-7] o.s.web.servlet.DispatcherServlet : No view rendering, null ModelAndView returned. 2020-09-08 21:50:07.966 DEBUG 14288 --- [nio-8080-exec-7] o.s.web.servlet.DispatcherServlet : Exiting from "FORWARD" dispatch, status 404, headers={masked} 2020-09-08 21:50:07.966 DEBUG 14288 --- [nio-8080-exec-7] o.s.web.servlet.DispatcherServlet : Completed 404 NOT_FOUND, headers={masked} 2020-09-08 21:50:07.966 TRACE 14288 --- [nio-8080-exec-7] o.s.web.servlet.DispatcherServlet : "ERROR" dispatch for GET "/error", parameters={}, headers={masked} in DispatcherServlet 'dispatcherServlet' 2020-09-08 21:50:07.967 TRACE 14288 --- [nio-8080-exec-7] s.w.s.m.m.a.RequestMappingHandlerMapping : 2 matching mappings: [{ /error, produces [text/html]}, { /error}] 2020-09-08 21:50:07.967 TRACE 14288 --- [nio-8080-exec-7] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController#errorHtml(HttpServletRequest, HttpServletResponse) 2020-09-08 21:50:07.967 TRACE 14288 --- [nio-8080-exec-7] .w.s.m.m.a.ServletInvocableHandlerMethod : Arguments: [org.apache.catalina.core.ApplicationHttpRequest@2ab43c7a, org.apache.catalina.connector.ResponseFacade@27bdc74b] 2020-09-08 21:50:07.968 TRACE 14288 --- [nio-8080-exec-7] o.s.w.s.v.InternalResourceViewResolver : View with key [error] served from cache 2020-09-08 21:50:07.968 DEBUG 14288 --- [nio-8080-exec-7] o.s.w.s.v.ContentNegotiatingViewResolver : Selected 'text/html' given [text/html, text/html;q=0.8] 2020-09-08 21:50:07.968 TRACE 14288 --- [nio-8080-exec-7] o.s.web.servlet.DispatcherServlet : Rendering view [org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration$StaticView@5ba1f9f8] 2020-09-08 21:50:07.968 DEBUG 14288 --- [nio-8080-exec-7] o.s.web.servlet.DispatcherServlet : Exiting from "ERROR" dispatch, status 404, headers={masked}
日志中十分直觀的說明了這兩個請求都經過spring進行了內部轉發(forward),區別是/defalut轉發的地址和/default/不同。/default/經過內部轉發的地址變成了/default/hello.html,也就是到templates目錄下的default目錄下去找hello.html,我們根本就沒有這個defaulf目錄,所以最終結果報錯,找不到這個文件。
現在再來思考一下測試2為什么會出現兩次映射結果?我們看一下測試2兩次映射的日志信息:
2020-09-09 08:27:18.058 TRACE 14788 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : GET "/default/", parameters={}, headers={masked} in DispatcherServlet 'dispatcherServlet' 2020-09-09 08:27:18.063 TRACE 14788 --- [nio-8080-exec-1] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to com.example.spring.modular.ReceiveParamController#def1(String) 2020-09-09 08:27:18.076 TRACE 14788 --- [nio-8080-exec-1] .w.s.m.m.a.ServletInvocableHandlerMethod : Arguments: [null] null 2020-09-09 08:27:22.312 DEBUG 14788 --- [nio-8080-exec-1] o.s.w.s.v.ContentNegotiatingViewResolver : Selected 'text/html' given [text/html, application/xhtml+xml, image/avif, image/webp, image/apng, application/xml;q=0.9, application/signed-exchange;v=b3;q=0.9, */*;q=0.8] 2020-09-09 08:27:22.312 TRACE 14788 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Rendering view [org.springframework.web.servlet.view.InternalResourceView: name 'hello.html'; URL [hello.html]] 2020-09-09 08:27:22.313 DEBUG 14788 --- [nio-8080-exec-1] o.s.w.servlet.view.InternalResourceView : View name 'hello.html', model {} 2020-09-09 08:27:22.315 DEBUG 14788 --- [nio-8080-exec-1] o.s.w.servlet.view.InternalResourceView : Forwarding to [hello.html] 2020-09-09 08:27:22.319 TRACE 14788 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : "FORWARD" dispatch for GET "/default/hello.html", parameters={}, headers={masked} in DispatcherServlet 'dispatcherServlet' 2020-09-09 08:27:22.320 TRACE 14788 --- [nio-8080-exec-1] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to com.example.spring.modular.ReceiveParamController#def2(String) 2020-09-09 08:27:22.320 TRACE 14788 --- [nio-8080-exec-1] .w.s.m.m.a.ServletInvocableHandlerMethod : Arguments: [null] null 2020-09-09 08:27:23.817 TRACE 14788 --- [nio-8080-exec-1] o.s.w.s.v.InternalResourceViewResolver : View with key [hello.html] served from cache 2020-09-09 08:27:23.818 DEBUG 14788 --- [nio-8080-exec-1] o.s.w.s.v.ContentNegotiatingViewResolver : Selected 'text/html' given [text/html, application/xhtml+xml, image/avif, image/webp, image/apng, application/xml;q=0.9, application/signed-exchange;v=b3;q=0.9, */*;q=0.8] 2020-09-09 08:27:23.818 TRACE 14788 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Rendering view [org.springframework.web.servlet.view.InternalResourceView: name 'hello.html'; URL [hello.html]] 2020-09-09 08:27:23.818 DEBUG 14788 --- [nio-8080-exec-1] o.s.w.servlet.view.InternalResourceView : View name 'hello.html', model {} 2020-09-09 08:27:23.825 DEBUG 14788 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Error rendering view [org.springframework.web.servlet.view.InternalResourceView: name 'hello.html'; URL [hello.html]]
從日志信息中可見,spring后端只獲取了一個請求路徑,第一次即先進入斷點1,斷點1執行的結果是內部forward到/default/hello.html,這個/default/hello.html竟然意外的能匹配上我們def2方法的/default/{id},這說明了什么?說明spring里{}是一個特殊字符,並不作為映射比對字符。現在我們將def2的mapping改成/default/hey,再次測試那個路徑,你會發現只進入斷點1不在進入斷點2了。
當我們把這兩個方法的return結果都改為"/hello.html"后就都能返回200狀態碼,且內部都只進行了一次轉發到根目錄下的hello.html文件。
1、方法上的RequestMapping如果以/結尾(不包括以/開頭且以/結尾的情況),如果返回的是一個頁面的話,該頁面沒有設置相對路徑,轉發的路由以獲取到的映射路徑(如日志中的GET "/default/")為父路徑加上返回的字符串路徑("hello.html"),即/default/hello.html,去查找新組合路徑下的頁面。如果返回的不是一個頁面,不受影響。
2、在測試2中先進入斷點1在進入斷點2,可知: {}是路徑匹配的特殊字符,並不作為映射比對字符。
controller里方法,在不加任何接收參數注解的情況下,默認參數映射僅支持get請求,且是query string里的參數即?id=3&age=4,參數映射規則是按照名稱一致匹配原則。
有測試7和8可知不加注解方式不能接受前端傳遞的json數據,也不能將前端傳遞的參數映射成map,也不可映射成list(如果是多個相同的key)。
由測試9可知,不加注解方式,Param里Query Params和Body里的form-data都可默認映射簡單對象User。
如果沒有加任何注解,在JDK1.8以下,是通過ASM框架對class文件進行解析,獲取spring mvc方法的參數信息,對參數數組賦值並緩存對應配置,方便下次請求時賦值。
@RequesParm常用來處理簡單類型的綁定,通過request.getParameter()獲取的String可直接轉換為簡單數據類型的情況(String ---> 簡單類型的轉換操作由ConversionService配置的轉換器來完成)。因為是使用request.getParameter()方式獲取參數,所以可以處理get方式中的queryString的值,也可以處理post方式中body data的值。既然是使用request.getParameter獲取參數,那可想而知參數是以key/value形式出現的,所以@RequestParam不僅可以直接在方法的形參里映射每一個參數,也可以用一個map映射到所有參數。
@RequestParam的作用:這個注解作為參數的配置信息存在,在第一次請求DispatchServlet時被初始化,在初始化之后,訪問這個方法時會先取出這個方法有幾個參數,參數的配置是什么,然后根據參數的配置和請求的參數對應,將值放入數組中,為之后的反射調用作准備並將關系解析值進行緩存方便下一次調用。
@Target({ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface RequestParam { @AliasFor("name") String value() default ""; @AliasFor("value") String name() default ""; boolean required() default true; String defaultValue() default "\n\t\t\n\t\t\n\ue000\ue001\ue002\n\t\t\t\t\n"; }
@RequestParam(value = "", defaultValue = "", required = true)
從源碼可以name的別名是value,value的別名又是name,所以這二者其實是等價的,用哪個都行。
required:是否必須包含該參數,默認為true表示包含,如果不包含就會出錯。
defaultValue:默認參數值,如果設置了該值,required=true將失效,自動為false,如果沒有傳該參數,就使用默認值。
為了驗證@RequestParam的有效性,我們使用它的required屬性:
package com.example.spring.modular; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import javax.servlet.http.HttpServletRequest; @Controller public class ReceiveParamController { @RequestMapping("/requestParam") public String requestParam(@RequestParam(value = "id", required = true) String id) { System.out.println(id); // 斷點3 return "/hello.html"; } @RequestMapping("/requestPm/{id}") public String requestParam2(@RequestParam(value = "id", required = true) String id) { System.out.println(id); // 斷點t return "/hello.html"; } @RequestMapping("/requestPar") public String requestPar(@RequestParam(value = "id", required = true) String id) { System.out.println(id); // 斷點p return "/hello.html"; } @RequestMapping("/requestParMap") public String requestPar(@RequestParam Map<String,String> map) { System.out.println(map); // 斷點q return "/hello.html"; } @RequestMapping("/requestParList") public String requestParList(@RequestParam List<String> list) { System.out.println(list); // 斷點r return "/hello.html"; } @RequestMapping("/requestParObject") public String requestParObject(@RequestParam User user) { System.out.println(user); // 斷點s return "/hello.html"; } }
postman使用get請求,請求地址為http://localhost:8080/requestParam,send請求
后端沒有進入斷點3位置,后端提示400 Bad Request。
curl --location --request GET 'http://localhost:8080/requestParam'
在依舊使用get請求,在Params里設置id=999的key和value,此時參數是默認加在請求url里的http://localhost:8080/requestParam?id=999,send請求。
后端進入斷點3位置並拿到參數999。返回了hello.html頁面。
curl --location --request GET 'http://localhost:8080/requestParam?id=999'
后端進入斷點3位置並拿到參數999。提示405 Method Not Allowed。
curl --location --request POST 'http://localhost:8080/requestParam?id=999'
依舊使用get請求,取消Params里的Query Params,選擇Body下的form-data,添加id=999的鍵值,然后send。
后端進入斷點3位置並拿到參數999。返回了hello.html頁面。
curl --location --request GET 'http://localhost:8080/requestParam' \ --form 'id=999'
切換成post請求,選擇Body下的form-data,添加id=999的鍵值,然后send。
后端進入斷點3位置並拿到參數999。提示405 Method Not Allowed。
curl --location --request POST 'http://localhost:8080/requestParam' \ --form 'id=999'
切換成get請求,取消form-data里的鍵值,選擇x-www-form-urlencoded,在里面添加id=999,send。
curl --location --request GET 'http://localhost:8080/requestParam' \ --header 'Content-Type: application/x-www-form-urlencoded' \ --data-urlencode 'id=999'
后端進入斷點3位置並拿到參數999。提示405 Method Not Allowed。
curl --location --request POST 'http://localhost:8080/requestParam' \ --header 'Content-Type: application/x-www-form-urlencoded' \ --data-urlencode 'id=999'
get請求,路徑為http://localhost:8080/requestParam/999,不進入斷點t,可知@RequestParam無法獲取@PathVariable里的路徑規則參數。
get請求,路徑為http://localhost:8080/requestPar,
無論斷點p和q所在方法是使用map接受還是只接受map里的一個string,發送請求都不會進入斷點p。只有當斷點q所在方法里的@RequestParam不指明具體參數名稱時才會進入斷點q,但是此刻map集合size為0(如果使用map作為形參)或id為null(以string作為形參)。
但是當我們不使用body-raw-JSON方式,而使用測試2、4、6時,也就是使用普通的query String、form-data、x-www-form-urlencoded時,此刻我們@RequestParam也不指定任何參數名稱,是可以進入斷點q的,直接映射為一個map,能使用map接受到前端傳遞的id參數為999,map的size為1。
curl --location --request POST 'http://localhost:8080/requestPar' \ --header 'Content-Type: application/json' \ --data-raw '{ "id":"999" }'
無論使用postman的那種方式,參數填入id/8,id/9,發送請求都不能進入斷點r位置。說明@RequestParam不可映射成list。
無論使用postman的那種方式,參數填入id/8,friends/a,friends/b或({"id":"999","friends":["a"]})發送請求都不能進入斷點s位置。說明不可映射成簡單對象User。
觀察測試結果,你會發現不同的請求方式,和不同的請求頭設置最終的都會影響到結果。對比測試3、5、7可得,對於post請求貌似spring mvc有一個限制。這里有一個知識點補充一下:在Spring MVC中POST方法不支持直接返回頁面,必須重定向(redirect)。如果是post請求需要return的結果為:"redirect:/hello.html"。當設置為重定向之后,測試3、5、7都能正常返回200的狀態碼。
對比測試2、3、4、5、7和6可知,對於get請求的x-www-form-urlencoded並沒有進入斷點,后台打印的日志如下:
2020-09-09 16:38:06.358 TRACE 20016 --- [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : GET "/requestParam", parameters={}, headers={masked} in DispatcherServlet 'dispatcherServlet' 2020-09-09 16:38:06.358 TRACE 20016 --- [nio-8080-exec-2] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to com.example.spring.modular.ReceiveParamController#requestParam1(String) 2020-09-09 16:38:06.358 DEBUG 20016 --- [nio-8080-exec-2] .w.s.m.m.a.ServletInvocableHandlerMethod : Could not resolve parameter [0] in public java.lang.String com.example.spring.modular.ReceiveParamController.requestParam1(java.lang.String): Required String parameter 'id' is not present 2020-09-09 16:38:06.358 WARN 20016 --- [nio-8080-exec-2] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.bind.MissingServletRequestParameterException: Required String parameter 'id' is not present] 2020-09-09 16:38:06.358 TRACE 20016 --- [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : No view rendering, null ModelAndView returned. 2020-09-09 16:38:06.358 DEBUG 20016 --- [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : Completed 400 BAD_REQUEST, headers={} 2020-09-09 16:38:06.359 TRACE 20016 --- [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : "ERROR" dispatch for GET "/error", parameters={}, headers={masked} in DispatcherServlet 'dispatcherServlet' 2020-09-09 16:38:06.359 TRACE 20016 --- [nio-8080-exec-2] s.w.s.m.m.a.RequestMappingHandlerMapping : 2 matching mappings: [{ /error}, { /error, produces [text/html]}] 2020-09-09 16:38:06.359 TRACE 20016 --- [nio-8080-exec-2] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController#error(HttpServletRequest) 2020-09-09 16:38:06.359 TRACE 20016 --- [nio-8080-exec-2] .w.s.m.m.a.ServletInvocableHandlerMethod : Arguments: [org.apache.catalina.core.ApplicationHttpRequest@6d9b77a9] 2020-09-09 16:38:06.359 DEBUG 20016 --- [nio-8080-exec-2] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Using 'application/json', given [*/*] and supported [application/json, application/*+json, application/json, application/*+json] 2020-09-09 16:38:06.359 TRACE 20016 --- [nio-8080-exec-2] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Writing [{timestamp=Wed Sep 09 16:38:06 CST 2020, status=400, error=Bad Request, message=, path=/requestParam}] 2020-09-09 16:38:06.360 TRACE 20016 --- [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : No view rendering, null ModelAndView returned. 2020-09-09 16:38:06.360 DEBUG 20016 --- [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : Exiting from "ERROR" dispatch, status 400, headers={masked}
可知測試6沒進入斷點是因為這個請求被required=true擋掉了,也就是說:@RequestParam無法獲取get請求的x-www-form-urlencoded參數,在對比一下測試6和測試7的postman發送請求的詳細信息,可以猜想到對於get請求情況下的x-www-form-urlencoded請求參數肯定是被spring處理了,導致接受不到參數。
由測試9可得,@RequestParam當不指定具體參數時,可以使用map接受form-data,x-www-form-urlencoded以及普通的query string參數,也不可映射成list(如果是多個相同的key)。
由測試10可知,@RequestParam當不指定具體參數時,不可將前端多個相同key的參數映射成一個List集合。
由測試11可知,@RequestParam不可映射簡單對象User。
該注解常用來處理Content-Type為application/json,application/xml等編碼的內容。它是通過使用HandlerAdapter配置的HttpMessageConverters來解析post data body,然后綁定到相應的bean上。因為配置有FormHttpMessageConverter,所以也可以用來處理application/x-www-form-urlencoded的內容,處理完的結果放在一個MultiValueMap<String,String>里,這種情況在某些特殊情況下使用,詳情查看FormHttpMessageConverter api;
@Target({ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface RequestBody { boolean required() default true; }
@RequestBody的語法相對簡單,直接在形參前加即可。
package com.example.spring.modular; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*; import javax.servlet.http.HttpServletRequest; import javax.websocket.server.PathParam; import java.util.List; import java.util.Map; @Controller public class ReceiveParamController { @RequestMapping(value = "/requestBody") public String requestBody(@RequestBody String id) { System.out.println(id); // 斷點1 return "/hello.html"; } @RequestMapping(value = "/requestBodyMap") public String requestBodyMap(@RequestBody Map<String, Object> map) { System.out.println(map); // 斷點2 return "/hello.html"; } @RequestMapping(value = "/requestBodyList") public String requestBodyList(@RequestBody List<Object> list) { System.out.println(list); // 斷點3 return "/hello.html"; } @RequestMapping(value = "/requestBodyObject") public String requestBodyObject(@RequestBody User user) { System.out.println(user); // 斷點4 return "/hello.html"; } @RequestMapping(value = "/requestBodyAndParamsList") public String requestBodyAndParamsList(@RequestBody User user, @RequestParam List<String> id) { System.out.println(user); // 斷點5 System.out.println(id); return "/hello.html"; } }
開頭介紹已經說明,基於@RequestBody的測試開始之前,我們先做一個預測試,分別在postman中使用Params下Query Params、Body下form-data添加key/value為id=999測試斷點1所在方法,發現均不進入斷點。
postman下,get請求http://localhost:8080/requestBody,選擇Body下的x-www-form-urlencoded,填入參數key/value為id/999,send。
進入斷點1位置,注意:此刻參數id不是"999"而是"id=999"
curl --location --request GET 'http://localhost:8080/requestBody' \ --header 'Content-Type: application/x-www-form-urlencoded' \ --data-urlencode 'id=999'
postman下,get請求http://localhost:8080/requestBody,選擇Body下的raw,下拉框選JSON,填入{"id":"999"},send。
進入斷點1位置,此刻參數id為:"{\r\n "id":999\r\n}"
postman下,get請求http://localhost:8080/requestBodyMap,選擇Body下的x-www-form-urlencoded,填入參數key/value為id/999,send。
不進入斷點2位置。后端提示Required request body is missing,400 Bad request。
postman下,get請求http://localhost:8080/requestBodyMap,選擇Body下的raw,下拉框選JSON,填入{"id":"999"},send。
進入斷點2位置,map的size為1,map.get("id")的value值為"999"
postman下,get請求http://localhost:8080/requestBodyList,只有選擇Body下的raw,下拉框選JSON,填入["a","b"],send,進入斷點3位置。其他情況無論選擇哪一種方式都不能進入斷點3,說明@RequestBody僅可接受application/json類型的List數據。
curl --location --request GET 'http://localhost:8080/requestBodyList' \ --header 'Content-Type: application/json' \ --data-raw '["id","key"]'
postman下,get請求http://localhost:8080/requestBodyObject,選擇Body下的x-www-form-urlencoded,填入參數key/value為id/999,send。
不進入斷點4位置。后端提示Required request body is missing,400 Bad request。
curl --location --request GET 'http://localhost:8080/requestBodyObject' \ --header 'Content-Type: application/x-www-form-urlencoded' \ --data-urlencode 'id=999' \ --data-urlencode 'friends=a'
postman下,get請求http://localhost:8080/requestBodyObject,選擇Body下的raw,下拉框選JSON,填入{"id":"999","friends":["a","b"]},send。
進入斷點4位置,user.getId()的值為"999",friends為一個不為空的ArrayList。
curl --location --request GET 'http://localhost:8080/requestBodyObject' \ --header 'Content-Type: application/json' \ --data-raw '{"id":"999","friends":["a","b"]}'
在@RequestParam測試的時候得知,該注解不可映射List參數,現在當它和@RequestBody組合使用時。結合測試5、6經驗,選擇Body下的raw,下拉框選JSON,填入{"id":"999","friends":["a","b"]},然后在Params下的Query Params里填入id/888,id/999,然后send。進入斷點5,此刻user被完全映射,集合List的id也包含兩個元素888和999。由此可知當@RequestBody和@RequestParam組合使用時,@RequestParam可以接受List參數。
curl --location --request GET 'http://localhost:8080/requestBodyAndParamsList' \ --header 'Content-Type: application/json' \ --data-raw '{"id":"999","friends":["a","b"]}'
由預測試和測試0和測試1可知,@RequestBody僅可接收x-www-form-urlencoded和application/json類型的數據,且映射參數的方式都是以map方式映射,如果形參使用字符串接收,則變為json字符串。
當形參使用map接收時,要注意傳遞的參數格式,由於會進行泛型這一點也可能導致你匹配不上。由測試2、3可知,map接受是只能接受application/json類型的數據。
由測試4可知,@RequestBody僅可接受applicationjson傳遞的List數據。
補充知識點:一個請求只能有一個@RequestBody但是可以有多個@RequestParam,
當@RequestBody和@RequestParam同時使用時:@RequestParam指定的元素除了它自己可以映射的普通元素、map外,變得可以映射集合List或數組。但注意它依舊不能映射對象User。
@Target({ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface PathVariable { @AliasFor("name") String value() default ""; @AliasFor("value") String name() default ""; boolean required() default true; }
name即value,required(默認為true)這些和@RequestParam類似,但是它沒有defaultValue。
@PathVariable(value = "id", required = true) # 配合@RequestMapping("/hello/{id}")
通過@PathVariable可以將URL中占位符參數綁定到處理器類的方法的形參中(如果Controller上的RequestMapping也有占位符,也可以取到)。要求@PathVariable中value指定的值必須可url路徑參數名稱對應才能映射。
package com.example.spring.modular; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*; import javax.servlet.http.HttpServletRequest; import javax.websocket.server.PathParam; import java.util.List; import java.util.Map; @Controller @RequestMapping("receive/{name}") public class ReceiveParamController { @RequestMapping("/pathVariable/{id}") public String pathVariable(@PathVariable("name") String name, @PathVariable(value = "id", required = true) String id) { System.out.println(id); return "/hello.html"; } }
關於@PathVariable中required設置為false的問題:
當某個形參的required設置為false是,你要多寫一個映射路徑,因為當這個參數不必要時那映射路徑肯定是不一樣的,例如:
package com.example.spring.modular; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*; import javax.servlet.http.HttpServletRequest; import javax.websocket.server.PathParam; import java.util.List; import java.util.Map; @Controller public class ReceiveParamController { @RequestMapping(value = {"/pathVariable/{id}/{name}", "/pathVariable/{name}"}) public String pathVariable(@PathVariable(value = "id", required = false) String id, @PathVariable("name") String name) { System.out.println(id); System.out.println(name); return "/hello.html"; } }
get請求地址為:http://localhost:8080/pathVariable/jack,請求打印id為null,name=jack
注意@PathParam不是Spring MVC用於獲取request請求參數的注解,不過它是JavaEE 6引入的新技術,JAX-RS即Java API for RESTful Web Services,該技術常用注解包括@Path、@GET、@POST、@PathParam、@QueryParam等。具體參見1、參見2,在這里不詳細研究。