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,在这里不详细研究。

 

 

posted @ 2020-09-16 09:39  刘呆哗  阅读(2063)  评论(0编辑  收藏  举报