SpringMVC接收請求參數區別


SpringMVC接收請求參數區別

 

 

 

 

基於spring mvc 5.2.8

 

參考學習:

幾種注解參數綁定區別: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

默認匹配

測試

原理分析

@RequestParam

語法

@RequestBody

語法

@PathVariable

語法

@PathParam

 

 

知識補充

Content-Type屬性指定請求和響應的HTTP內容類型。如果未指定ContentType,默認響應的內容類型為 text/html,默認請求的內容類型為 application/x-www-form-urlencoded。Content-Type一般只存在於Post方法中,因為Get方法是不含“body”的,它的請求參數都會被編碼到url后面,所以在Get方法中加Content-Type是無效的。

 

常見的Content-Type

 text/html

 text/plain

 text/css

 text/javascript

 application/x-www-form-urlencoded

 multipart/form-data

 application/json

 application/xml

前面幾個為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。

 

 

默認匹配

 

測試

創建一個簡單的springboot項目

 

 

User.java

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";
    }

}

  

測試0:

瀏覽器地址輸入:http://localhost:8080/de 、 http://localhost:8080/defa

后端沒有任何斷點進入,頁面報404 error。

測試1:

瀏覽器地址輸入:http://localhost:8080/default

后端只進入斷點位置1。【id參數為null】

測試2:

瀏覽器地址輸入:http://localhost:8080/default/

后端先進入斷點位置1然后進入斷點位置2,然后在進入斷點位置2,最終結果報錯:循環視圖路徑。【id參數始終為null】

為了解決這個問題,我們在return的html之前加上一個/,表示轉發的路由以跟路徑開頭,這樣就不會出現循環視圖的問題了。

改為"/hello.html"后,后端只進入斷點位置1。【id參數為null】

測試3:

瀏覽器地址輸入:http://localhost:8080/default?id=1

后端只進入斷點位置1。【id參數為1】

測試4:

瀏覽器地址輸入:http://localhost:8080/default/?id=1

后端先進入斷點位置1然后進入斷點位置2,然后在進入斷點位置2,最終結果報錯:循環視圖路徑。【id參數始終為"1"】

改為"/hello.html"后,后端只進入斷點位置1。【id參數為1】

測試5:

瀏覽器地址輸入:http://localhost:8080/default/3?id=1

后端先進入斷點位置2,然后在進入斷點位置2,最終結果報錯:循環視圖路徑。【id參數始終為"1"】

改為"/hello.html"后,后端只進入斷點位置2。【id參數為1】

測試6:

瀏覽器地址輸入:http://localhost:8080/default/3/?id=1

后端只進入一次斷點位置2,然后結果報錯:循環視圖路徑。【id參數為"1"】

改為"/hello.html"后,后端只進入斷點位置2。【id參數為1】

測試7:

使用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"
}'

  

測試8:

無論使用postman的那種方式,參數填入id/8,id/9,發送請求都不能進入斷點def4位置。說明不可映射成list

測試9:

使用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。

 

 

結論1:

由測試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/defaulthttp://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文件。

 

結論2:

1、方法上的RequestMapping如果以/結尾(不包括以/開頭且以/結尾的情況),如果返回的是一個頁面的話,該頁面沒有設置相對路徑,轉發的路由以獲取到的映射路徑(如日志中的GET "/default/")為父路徑加上返回的字符串路徑("hello.html"),即/default/hello.html,去查找新組合路徑下的頁面。如果返回的不是一個頁面,不受影響。

2、在測試2中先進入斷點1在進入斷點2,可知: {}是路徑匹配的特殊字符,並不作為映射比對字符。

 

結論3:

controller里方法,在不加任何接收參數注解的情況下,默認參數映射僅支持get請求,且是query string里的參數即?id=3&age=4,參數映射規則是按照名稱一致匹配原則。

 

結論4:

有測試7和8可知不加注解方式不能接受前端傳遞的json數據,也不能將前端傳遞的參數映射成map,也不可映射成list(如果是多個相同的key)。

 

結論5:

由測試9可知,不加注解方式,Param里Query Params和Body里的form-data都可默認映射簡單對象User。

 

原理分析

不加任何注解spring mvc是如何進行參數映射的?

參考1參考2

如果沒有加任何注解,在JDK1.8以下,是通過ASM框架對class文件進行解析,獲取spring mvc方法的參數信息,對參數數組賦值並緩存對應配置,方便下次請求時賦值。

 

 

 

@RequestParam

@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,所以這二者其實是等價的,用哪個都行。

value:表示映射到路由里的參數名稱。

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,開始測試:

測試1:

postman使用get請求,請求地址為http://localhost:8080/requestParamsend請求

后端沒有進入斷點3位置,后端提示400 Bad Request。

curl --location --request GET 'http://localhost:8080/requestParam'

  

測試2:

在依舊使用get請求,在Params里設置id=999的key和value,此時參數是默認加在請求url里的http://localhost:8080/requestParam?id=999send請求。

后端進入斷點3位置並拿到參數999。返回了hello.html頁面。

curl --location --request GET 'http://localhost:8080/requestParam?id=999'

  

測試3:

更改為post請求,其他不變。

后端進入斷點3位置並拿到參數999。提示405 Method Not Allowed。

curl --location --request POST 'http://localhost:8080/requestParam?id=999'

  

測試4:

依舊使用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'

  

測試5:

切換成post請求,選擇Body下的form-data,添加id=999的鍵值,然后send。

后端進入斷點3位置並拿到參數999。提示405 Method Not Allowed。

curl --location --request POST 'http://localhost:8080/requestParam' \
--form 'id=999'

  

測試6:

切換成get請求,取消form-data里的鍵值,選擇x-www-form-urlencoded,在里面添加id=999,send。

后面沒有進入斷點位置,提示400 Bad Request。

curl --location --request GET 'http://localhost:8080/requestParam' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'id=999'

  

測試7:

切換成post請求,其他不變。

后端進入斷點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'

  

測試8:

get請求,路徑為http://localhost:8080/requestParam/999,不進入斷點t,可知@RequestParam無法獲取@PathVariable里的路徑規則參數。

測試9:

get請求,路徑為http://localhost:8080/requestPar

選擇body-raw-JSON,填入:{"id":999}

無論斷點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"
}'

  

測試10:

無論使用postman的那種方式,參數填入id/8,id/9,發送請求都不能進入斷點r位置。說明@RequestParam不可映射成list。

測試11:

無論使用postman的那種方式,參數填入id/8,friends/a,friends/b或({"id":"999","friends":["a"]})發送請求都不能進入斷點s位置。說明不可映射成簡單對象User。

 

結論1:

觀察測試結果,你會發現不同的請求方式,和不同的請求頭設置最終的都會影響到結果。對比測試3、5、7可得,對於post請求貌似spring mvc有一個限制。這里有一個知識點補充一下:Spring MVC中POST方法不支持直接返回頁面,必須重定向(redirect)。如果是post請求需要return的結果為:"redirect:/hello.html"。當設置為重定向之后,測試3、5、7都能正常返回200的狀態碼。

結論2:

對比測試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處理了,導致接受不到參數。

結論3:

由測試9可得,@RequestParam當不指定具體參數時,可以使用map接受form-data,x-www-form-urlencoded以及普通的query string參數,也不可映射成list(如果是多個相同的key)。

由測試10可知,@RequestParam當不指定具體參數時,不可將前端多個相同key的參數映射成一個List集合。

結論4:

由測試11可知,@RequestParam不可映射簡單對象User。

 

@RequestBody

該注解常用來處理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所在方法,發現均不進入斷點。

測試0:

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'

  

測試1:

postman下,get請求http://localhost:8080/requestBody,選擇Body下的raw,下拉框選JSON,填入{"id":"999"},send。

進入斷點1位置,此刻參數id為:"{\r\n  "id":999\r\n}"

測試2:

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。

測試3:

postman下,get請求http://localhost:8080/requestBodyMap,選擇Body下的raw,下拉框選JSON,填入{"id":"999"},send。

進入斷點2位置,map的size為1,map.get("id")的value值為"999"

測試4:

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"]'

  

測試5:

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'

  

測試6:

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"]}'

  

測試7:

@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"]}'

  

結論1:

由預測試和測試0和測試1可知,@RequestBody僅可接收x-www-form-urlencoded和application/json類型的數據,且映射參數的方式都是以map方式映射,如果形參使用字符串接收,則變為json字符串。

 

結論2:

當形參使用map接收時,要注意傳遞的參數格式,由於會進行泛型這一點也可能導致你匹配不上。由測試2、3可知,map接受是只能接受application/json類型的數據。

 

結論3:

由測試4可知,@RequestBody僅可接受applicationjson傳遞的List數據。

 

結論4:

補充知識點:一個請求只能有一個@RequestBody但是可以有多個@RequestParam,

@RequestBody和@RequestParam同時使用時:@RequestParam指定的元素除了它自己可以映射的普通元素、map外,變得可以映射集合List或數組。但注意它依舊不能映射對象User。

 

 

@PathVariable

顧名思義,pathVariable即:路徑變量。

 

語法

@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

 

 

@ModelAttribute

具體參考頂部文章:全面解析@ModelArrribute

 

 

@SessionAttribute

參見參考學習第一篇文章。

 

 

@PathParam

注意@PathParam不是Spring MVC用於獲取request請求參數的注解,不過它是JavaEE 6引入的新技術,JAX-RS即Java API for RESTful Web Services,該技術常用注解包括@Path、@GET、@POST、@PathParam、@QueryParam等。具體參見1參見2,在這里不詳細研究。

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM