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

     

    @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为:"{  "id":999 }"

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

     

     

    前进时,请别遗忘了身后的脚印。
  • 相关阅读:
    统计学习方法学习笔记(一)--极大似然估计与贝叶斯估计原理及区别
    数据过拟合解决方法
    LSTM基础
    异方差产生与解决
    人工免疫相关算法
    Svm相关
    sscanf,sscanf_s及其相关用法
    C语言数组初始化
    生产者和消费者
    Linux线程-创建
  • 原文地址:https://www.cnblogs.com/liudaihuablogs/p/13677355.html
Copyright © 2020-2023  润新知