几种注解参数绑定区别:https://www.cnblogs.com/guoyinli/p/7056146.html
@RequestBody的使用:https://blog.csdn.net/justry_deng/article/details/80972817
全面解析@ModelArrribute:https://www.cnblogs.com/cobcmw/p/12092591.html
Content-Type属性指定请求和响应的HTTP内容类型。如果未指定ContentType,默认响应的内容类型为 text/html,默认请求的内容类型为 application/x-www-form-urlencoded。Content-Type一般只存在于Post方法中,因为Get方法是不含“body”的,它的请求参数都会被编码到url后面,所以在Get方法中加Content-Type是无效的。
application/x-www-form-urlencoded
前面几个为html、纯文本、css、javascript的文件类型。后面四个则是POST的发包方式。
application/x-www-form-urlencoded 是常见的表单发包方式,普通的表单提交,或者js发包,默认都是这种方式,数据被编码为key/value格式发送到服务器。
multipart/form-data 用在发送文件的POST包。multipart/form-data的请求头必须包含一个特殊的头信息:content-type,且其值也必须规定为multipart/form-data,同时还需要规定一个内容分隔符即boundary用于分割请求体中的多个POST的内容,如文件内容和文本内容自然需要分割开来,不然接收方法就无法正常解析和还原这个文件了(关于boundary可参加这里)。
application/json HTTP通信中并不存在所谓的json,而是将string转成json罢了,所以application/json也可以理解成text/plain。
package com.example.spring.modular; import java.util.List; public class User { private String id; private List<String> friends; public String getId() { return id; } public void setId(String id) { this.id = id; } public List<String> getFriends() { return friends; } public void setFriends(List<String> friends) { this.friends = friends; } }
然后创建如下controller,先来看一下spring默认的匹配策略。
package com.example.spring.modular; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @Controller public class ReceiveParamController { @RequestMapping("/def") public String def0(String id) { System.out.println(id); // 断点位置0 return "hello.html"; // 改为"/hello.html" } @RequestMapping("/default") public String def1(String id) { System.out.println(id); // 断点位置1 return "hello.html"; // 改为"/hello.html" } @RequestMapping("/default/{id}") public String def2(String id) { System.out.println(id); // 断点位置2 return "hello.html"; // 改为"/hello.html" } @RequestMapping("/def3") public String def3(Map<String, String> map) { System.out.println(map); // 断点位置def3 return "/hello.html"; } @RequestMapping("/def4") public String def4(List<String> list) { System.out.println(list); // 断点位置def4 return "/hello.html"; } @RequestMapping("/def5") public String def5(User user) { System.out.println(user); // 断点位置def5 return "/hello.html"; } }
浏览器地址输入:http://localhost:8080/de 、 http://localhost:8080/defa
浏览器地址输入:http://localhost:8080/default
浏览器地址输入:http://localhost:8080/default/
后端先进入断点位置1然后进入断点位置2,然后在进入断点位置2,最终结果报错:循环视图路径。【id参数始终为null】
为了解决这个问题,我们在return的html之前加上一个/,表示转发的路由以跟路径开头,这样就不会出现循环视图的问题了。
改为"/hello.html"后,后端只进入断点位置1。【id参数为null】
浏览器地址输入:http://localhost:8080/default?id=1
浏览器地址输入:http://localhost:8080/default/?id=1
后端先进入断点位置1然后进入断点位置2,然后在进入断点位置2,最终结果报错:循环视图路径。【id参数始终为"1"】
改为"/hello.html"后,后端只进入断点位置1。【id参数为1】
浏览器地址输入:http://localhost:8080/default/3?id=1
后端先进入断点位置2,然后在进入断点位置2,最终结果报错:循环视图路径。【id参数始终为"1"】
改为"/hello.html"后,后端只进入断点位置2。【id参数为1】
浏览器地址输入:http://localhost:8080/default/3/?id=1
后端只进入一次断点位置2,然后结果报错:循环视图路径。【id参数为"1"】
改为"/hello.html"后,后端只进入断点位置2。【id参数为1】
使用postman,选择body-raw-JSON,输入:{"id":"1"},send,进入断点位置def3,但是map里参数size为0,没有收到参数。除此之外,在postman中无论使用Params的Query Params方式,还是使用Body下的每一种方式,都无法接收到参数。
curl --location --request GET 'http://localhost:8080/def3' --header 'Content-Type: application/json' --data-raw '{ "id":"999" }'
无论使用postman的那种方式,参数填入id/8,id/9,发送请求都不能进入断点def4位置。说明不可映射成list
使用postman,发送get请求http://localhost:8080/def5,当选择Params下的Query Params和Body下from-data,参数key/value填入id/999,firends/a,friends/b时能成功映射user的id为999,friends为一个不为null的list集合,其他情况如:Body下x-www-form-urlencoded和Body下raw,JSON下{"id":"999","friends":["a","b"]},虽然也能进入方法,但是映射的user.id和friends都为null。
由测试0和测试1可得,spring默认的路由匹配为精准匹配,然后才是最长匹配。
为了更好的对比测试1和测试2,我们在application.yml增加如下,让spring打印更详细的日志信息:
# 表明静态资源的位置 spring: resources: static-locations: classpath:/templates/ # 新增更详细的日志信息 logging: level: root: info org.springframework.web: trace
@RequestMapping("/default") public String def1(String id) { System.out.println(id); return "hello.html"; // 没改之前 } @RequestMapping("/default/") public String def2(String id) { System.out.println(id); return "hello.html"; // 没改之前 }
依次访问http://localhost:8080/default和http://localhost:8080/default/打印日志信息分别如下:
2020-09-08 21:49:49.321 TRACE 14288 --- [nio-8080-exec-5] o.s.web.servlet.DispatcherServlet : GET "/default", parameters={}, headers={masked} in DispatcherServlet 'dispatcherServlet' 2020-09-08 21:49:49.322 TRACE 14288 --- [nio-8080-exec-5] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to com.example.spring.modular.ReceiveParamController#def1(String) 2020-09-08 21:49:49.322 TRACE 14288 --- [nio-8080-exec-5] .w.s.m.m.a.ServletInvocableHandlerMethod : Arguments: [null] null 2020-09-08 21:49:49.323 TRACE 14288 --- [nio-8080-exec-5] o.s.w.s.v.InternalResourceViewResolver : View with key [hello.html] served from cache 2020-09-08 21:49:49.323 DEBUG 14288 --- [nio-8080-exec-5] o.s.w.s.v.ContentNegotiatingViewResolver : Selected 'text/html' given [text/html, application/xhtml+xml, image/webp, image/apng, application/signed-exchange;v=b3, application/xml;q=0.9, */*;q=0.8] 2020-09-08 21:49:49.323 TRACE 14288 --- [nio-8080-exec-5] o.s.web.servlet.DispatcherServlet : Rendering view [org.springframework.web.servlet.view.InternalResourceView: name 'hello.html'; URL [hello.html]] 2020-09-08 21:49:49.323 DEBUG 14288 --- [nio-8080-exec-5] o.s.w.servlet.view.InternalResourceView : View name 'hello.html', model {} 2020-09-08 21:49:49.323 DEBUG 14288 --- [nio-8080-exec-5] o.s.w.servlet.view.InternalResourceView : Forwarding to [hello.html] 2020-09-08 21:49:49.323 TRACE 14288 --- [nio-8080-exec-5] o.s.web.servlet.DispatcherServlet : "FORWARD" dispatch for GET "/hello.html", parameters={}, headers={masked} in DispatcherServlet 'dispatcherServlet' 2020-09-08 21:49:49.324 TRACE 14288 --- [nio-8080-exec-5] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped to HandlerExecutionChain with [ResourceHttpRequestHandler ["classpath:/templates/", "/"]] and 3 interceptors 2020-09-08 21:49:49.326 TRACE 14288 --- [nio-8080-exec-5] o.s.web.servlet.DispatcherServlet : No view rendering, null ModelAndView returned. 2020-09-08 21:49:49.326 DEBUG 14288 --- [nio-8080-exec-5] o.s.web.servlet.DispatcherServlet : Exiting from "FORWARD" dispatch, status 200, headers={masked} 2020-09-08 21:49:49.326 DEBUG 14288 --- [nio-8080-exec-5] o.s.web.servlet.DispatcherServlet : Completed 200 OK, headers={masked} ================================================================================================================================================================================================================================================= 2020-09-08 21:50:07.960 TRACE 14288 --- [nio-8080-exec-7] o.s.web.servlet.DispatcherServlet : GET "/default/", parameters={}, headers={masked} in DispatcherServlet 'dispatcherServlet' 2020-09-08 21:50:07.961 TRACE 14288 --- [nio-8080-exec-7] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to com.example.spring.modular.ReceiveParamController#def2(String) 2020-09-08 21:50:07.961 TRACE 14288 --- [nio-8080-exec-7] .w.s.m.m.a.ServletInvocableHandlerMethod : Arguments: [null] null 2020-09-08 21:50:07.962 TRACE 14288 --- [nio-8080-exec-7] o.s.w.s.v.InternalResourceViewResolver : View with key [hello.html] served from cache 2020-09-08 21:50:07.963 DEBUG 14288 --- [nio-8080-exec-7] o.s.w.s.v.ContentNegotiatingViewResolver : Selected 'text/html' given [text/html, application/xhtml+xml, image/webp, image/apng, application/signed-exchange;v=b3, application/xml;q=0.9, */*;q=0.8] 2020-09-08 21:50:07.963 TRACE 14288 --- [nio-8080-exec-7] o.s.web.servlet.DispatcherServlet : Rendering view [org.springframework.web.servlet.view.InternalResourceView: name 'hello.html'; URL [hello.html]] 2020-09-08 21:50:07.963 DEBUG 14288 --- [nio-8080-exec-7] o.s.w.servlet.view.InternalResourceView : View name 'hello.html', model {} 2020-09-08 21:50:07.963 DEBUG 14288 --- [nio-8080-exec-7] o.s.w.servlet.view.InternalResourceView : Forwarding to [hello.html] 2020-09-08 21:50:07.963 TRACE 14288 --- [nio-8080-exec-7] o.s.web.servlet.DispatcherServlet : "FORWARD" dispatch for GET "/default/hello.html", parameters={}, headers={masked} in DispatcherServlet 'dispatcherServlet' 2020-09-08 21:50:07.964 TRACE 14288 --- [nio-8080-exec-7] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped to HandlerExecutionChain with [ResourceHttpRequestHandler ["classpath:/templates/", "/"]] and 3 interceptors 2020-09-08 21:50:07.966 DEBUG 14288 --- [nio-8080-exec-7] o.s.w.s.r.ResourceHttpRequestHandler : Resource not found 2020-09-08 21:50:07.966 TRACE 14288 --- [nio-8080-exec-7] o.s.web.servlet.DispatcherServlet : No view rendering, null ModelAndView returned. 2020-09-08 21:50:07.966 DEBUG 14288 --- [nio-8080-exec-7] o.s.web.servlet.DispatcherServlet : Exiting from "FORWARD" dispatch, status 404, headers={masked} 2020-09-08 21:50:07.966 DEBUG 14288 --- [nio-8080-exec-7] o.s.web.servlet.DispatcherServlet : Completed 404 NOT_FOUND, headers={masked} 2020-09-08 21:50:07.966 TRACE 14288 --- [nio-8080-exec-7] o.s.web.servlet.DispatcherServlet : "ERROR" dispatch for GET "/error", parameters={}, headers={masked} in DispatcherServlet 'dispatcherServlet' 2020-09-08 21:50:07.967 TRACE 14288 --- [nio-8080-exec-7] s.w.s.m.m.a.RequestMappingHandlerMapping : 2 matching mappings: [{ /error, produces [text/html]}, { /error}] 2020-09-08 21:50:07.967 TRACE 14288 --- [nio-8080-exec-7] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController#errorHtml(HttpServletRequest, HttpServletResponse) 2020-09-08 21:50:07.967 TRACE 14288 --- [nio-8080-exec-7] .w.s.m.m.a.ServletInvocableHandlerMethod : Arguments: [org.apache.catalina.core.ApplicationHttpRequest@2ab43c7a, org.apache.catalina.connector.ResponseFacade@27bdc74b] 2020-09-08 21:50:07.968 TRACE 14288 --- [nio-8080-exec-7] o.s.w.s.v.InternalResourceViewResolver : View with key [error] served from cache 2020-09-08 21:50:07.968 DEBUG 14288 --- [nio-8080-exec-7] o.s.w.s.v.ContentNegotiatingViewResolver : Selected 'text/html' given [text/html, text/html;q=0.8] 2020-09-08 21:50:07.968 TRACE 14288 --- [nio-8080-exec-7] o.s.web.servlet.DispatcherServlet : Rendering view [org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration$StaticView@5ba1f9f8] 2020-09-08 21:50:07.968 DEBUG 14288 --- [nio-8080-exec-7] o.s.web.servlet.DispatcherServlet : Exiting from "ERROR" dispatch, status 404, headers={masked}
日志中十分直观的说明了这两个请求都经过spring进行了内部转发(forward),区别是/defalut转发的地址和/default/不同。/default/经过内部转发的地址变成了/default/hello.html,也就是到templates目录下的default目录下去找hello.html,我们根本就没有这个defaulf目录,所以最终结果报错,找不到这个文件。
现在再来思考一下测试2为什么会出现两次映射结果?我们看一下测试2两次映射的日志信息:
2020-09-09 08:27:18.058 TRACE 14788 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : GET "/default/", parameters={}, headers={masked} in DispatcherServlet 'dispatcherServlet' 2020-09-09 08:27:18.063 TRACE 14788 --- [nio-8080-exec-1] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to com.example.spring.modular.ReceiveParamController#def1(String) 2020-09-09 08:27:18.076 TRACE 14788 --- [nio-8080-exec-1] .w.s.m.m.a.ServletInvocableHandlerMethod : Arguments: [null] null 2020-09-09 08:27:22.312 DEBUG 14788 --- [nio-8080-exec-1] o.s.w.s.v.ContentNegotiatingViewResolver : Selected 'text/html' given [text/html, application/xhtml+xml, image/avif, image/webp, image/apng, application/xml;q=0.9, application/signed-exchange;v=b3;q=0.9, */*;q=0.8] 2020-09-09 08:27:22.312 TRACE 14788 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Rendering view [org.springframework.web.servlet.view.InternalResourceView: name 'hello.html'; URL [hello.html]] 2020-09-09 08:27:22.313 DEBUG 14788 --- [nio-8080-exec-1] o.s.w.servlet.view.InternalResourceView : View name 'hello.html', model {} 2020-09-09 08:27:22.315 DEBUG 14788 --- [nio-8080-exec-1] o.s.w.servlet.view.InternalResourceView : Forwarding to [hello.html] 2020-09-09 08:27:22.319 TRACE 14788 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : "FORWARD" dispatch for GET "/default/hello.html", parameters={}, headers={masked} in DispatcherServlet 'dispatcherServlet' 2020-09-09 08:27:22.320 TRACE 14788 --- [nio-8080-exec-1] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to com.example.spring.modular.ReceiveParamController#def2(String) 2020-09-09 08:27:22.320 TRACE 14788 --- [nio-8080-exec-1] .w.s.m.m.a.ServletInvocableHandlerMethod : Arguments: [null] null 2020-09-09 08:27:23.817 TRACE 14788 --- [nio-8080-exec-1] o.s.w.s.v.InternalResourceViewResolver : View with key [hello.html] served from cache 2020-09-09 08:27:23.818 DEBUG 14788 --- [nio-8080-exec-1] o.s.w.s.v.ContentNegotiatingViewResolver : Selected 'text/html' given [text/html, application/xhtml+xml, image/avif, image/webp, image/apng, application/xml;q=0.9, application/signed-exchange;v=b3;q=0.9, */*;q=0.8] 2020-09-09 08:27:23.818 TRACE 14788 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Rendering view [org.springframework.web.servlet.view.InternalResourceView: name 'hello.html'; URL [hello.html]] 2020-09-09 08:27:23.818 DEBUG 14788 --- [nio-8080-exec-1] o.s.w.servlet.view.InternalResourceView : View name 'hello.html', model {} 2020-09-09 08:27:23.825 DEBUG 14788 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Error rendering view [org.springframework.web.servlet.view.InternalResourceView: name 'hello.html'; URL [hello.html]]
从日志信息中可见,spring后端只获取了一个请求路径,第一次即先进入断点1,断点1执行的结果是内部forward到/default/hello.html,这个/default/hello.html竟然意外的能匹配上我们def2方法的/default/{id},这说明了什么?说明spring里{}是一个特殊字符,并不作为映射比对字符。现在我们将def2的mapping改成/default/hey,再次测试那个路径,你会发现只进入断点1不在进入断点2了。
当我们把这两个方法的return结果都改为"/hello.html"后就都能返回200状态码,且内部都只进行了一次转发到根目录下的hello.html文件。
1、方法上的RequestMapping如果以/结尾(不包括以/开头且以/结尾的情况),如果返回的是一个页面的话,该页面没有设置相对路径,转发的路由以获取到的映射路径(如日志中的GET "/default/")为父路径加上返回的字符串路径("hello.html"),即/default/hello.html,去查找新组合路径下的页面。如果返回的不是一个页面,不受影响。
2、在测试2中先进入断点1在进入断点2,可知: {}是路径匹配的特殊字符,并不作为映射比对字符。
controller里方法,在不加任何接收参数注解的情况下,默认参数映射仅支持get请求,且是query string里的参数即?id=3&age=4,参数映射规则是按照名称一致匹配原则。
有测试7和8可知不加注解方式不能接受前端传递的json数据,也不能将前端传递的参数映射成map,也不可映射成list(如果是多个相同的key)。
由测试9可知,不加注解方式,Param里Query Params和Body里的form-data都可默认映射简单对象User。
如果没有加任何注解,在JDK1.8以下,是通过ASM框架对class文件进行解析,获取spring mvc方法的参数信息,对参数数组赋值并缓存对应配置,方便下次请求时赋值。
@RequesParm常用来处理简单类型的绑定,通过request.getParameter()获取的String可直接转换为简单数据类型的情况(String ---> 简单类型的转换操作由ConversionService配置的转换器来完成)。因为是使用request.getParameter()方式获取参数,所以可以处理get方式中的queryString的值,也可以处理post方式中body data的值。既然是使用request.getParameter获取参数,那可想而知参数是以key/value形式出现的,所以@RequestParam不仅可以直接在方法的形参里映射每一个参数,也可以用一个map映射到所有参数。
@RequestParam的作用:这个注解作为参数的配置信息存在,在第一次请求DispatchServlet时被初始化,在初始化之后,访问这个方法时会先取出这个方法有几个参数,参数的配置是什么,然后根据参数的配置和请求的参数对应,将值放入数组中,为之后的反射调用作准备并将关系解析值进行缓存方便下一次调用。
@Target({ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface RequestParam { @AliasFor("name") String value() default ""; @AliasFor("value") String name() default ""; boolean required() default true; String defaultValue() default " ue000ue001ue002 "; }
@RequestParam(value = "", defaultValue = "", required = true)
从源码可以name的别名是value,value的别名又是name,所以这二者其实是等价的,用哪个都行。
required:是否必须包含该参数,默认为true表示包含,如果不包含就会出错。
defaultValue:默认参数值,如果设置了该值,required=true将失效,自动为false,如果没有传该参数,就使用默认值。
为了验证@RequestParam的有效性,我们使用它的required属性:
package com.example.spring.modular; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import javax.servlet.http.HttpServletRequest; @Controller public class ReceiveParamController { @RequestMapping("/requestParam") public String requestParam(@RequestParam(value = "id", required = true) String id) { System.out.println(id); // 断点3 return "/hello.html"; } @RequestMapping("/requestPm/{id}") public String requestParam2(@RequestParam(value = "id", required = true) String id) { System.out.println(id); // 断点t return "/hello.html"; } @RequestMapping("/requestPar") public String requestPar(@RequestParam(value = "id", required = true) String id) { System.out.println(id); // 断点p return "/hello.html"; } @RequestMapping("/requestParMap") public String requestPar(@RequestParam Map<String,String> map) { System.out.println(map); // 断点q return "/hello.html"; } @RequestMapping("/requestParList") public String requestParList(@RequestParam List<String> list) { System.out.println(list); // 断点r return "/hello.html"; } @RequestMapping("/requestParObject") public String requestParObject(@RequestParam User user) { System.out.println(user); // 断点s return "/hello.html"; } }
postman使用get请求,请求地址为http://localhost:8080/requestParam,send请求
后端没有进入断点3位置,后端提示400 Bad Request。
curl --location --request GET 'http://localhost:8080/requestParam'
在依旧使用get请求,在Params里设置id=999的key和value,此时参数是默认加在请求url里的http://localhost:8080/requestParam?id=999,send请求。
后端进入断点3位置并拿到参数999。返回了hello.html页面。
curl --location --request GET 'http://localhost:8080/requestParam?id=999'
后端进入断点3位置并拿到参数999。提示405 Method Not Allowed。
curl --location --request POST 'http://localhost:8080/requestParam?id=999'
依旧使用get请求,取消Params里的Query Params,选择Body下的form-data,添加id=999的键值,然后send。
后端进入断点3位置并拿到参数999。返回了hello.html页面。
curl --location --request GET 'http://localhost:8080/requestParam' --form 'id=999'
切换成post请求,选择Body下的form-data,添加id=999的键值,然后send。
后端进入断点3位置并拿到参数999。提示405 Method Not Allowed。
curl --location --request POST 'http://localhost:8080/requestParam' --form 'id=999'
切换成get请求,取消form-data里的键值,选择x-www-form-urlencoded,在里面添加id=999,send。
curl --location --request GET 'http://localhost:8080/requestParam' --header 'Content-Type: application/x-www-form-urlencoded' --data-urlencode 'id=999'
后端进入断点3位置并拿到参数999。提示405 Method Not Allowed。
curl --location --request POST 'http://localhost:8080/requestParam' --header 'Content-Type: application/x-www-form-urlencoded' --data-urlencode 'id=999'
get请求,路径为http://localhost:8080/requestParam/999,不进入断点t,可知@RequestParam无法获取@PathVariable里的路径规则参数。
get请求,路径为http://localhost:8080/requestPar,
无论断点p和q所在方法是使用map接受还是只接受map里的一个string,发送请求都不会进入断点p。只有当断点q所在方法里的@RequestParam不指明具体参数名称时才会进入断点q,但是此刻map集合size为0(如果使用map作为形参)或id为null(以string作为形参)。
但是当我们不使用body-raw-JSON方式,而使用测试2、4、6时,也就是使用普通的query String、form-data、x-www-form-urlencoded时,此刻我们@RequestParam也不指定任何参数名称,是可以进入断点q的,直接映射为一个map,能使用map接受到前端传递的id参数为999,map的size为1。
curl --location --request POST 'http://localhost:8080/requestPar' --header 'Content-Type: application/json' --data-raw '{ "id":"999" }'
无论使用postman的那种方式,参数填入id/8,id/9,发送请求都不能进入断点r位置。说明@RequestParam不可映射成list。
无论使用postman的那种方式,参数填入id/8,friends/a,friends/b或({"id":"999","friends":["a"]})发送请求都不能进入断点s位置。说明不可映射成简单对象User。
观察测试结果,你会发现不同的请求方式,和不同的请求头设置最终的都会影响到结果。对比测试3、5、7可得,对于post请求貌似spring mvc有一个限制。这里有一个知识点补充一下:在Spring MVC中POST方法不支持直接返回页面,必须重定向(redirect)。如果是post请求需要return的结果为:"redirect:/hello.html"。当设置为重定向之后,测试3、5、7都能正常返回200的状态码。
对比测试2、3、4、5、7和6可知,对于get请求的x-www-form-urlencoded并没有进入断点,后台打印的日志如下:
2020-09-09 16:38:06.358 TRACE 20016 --- [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : GET "/requestParam", parameters={}, headers={masked} in DispatcherServlet 'dispatcherServlet' 2020-09-09 16:38:06.358 TRACE 20016 --- [nio-8080-exec-2] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to com.example.spring.modular.ReceiveParamController#requestParam1(String) 2020-09-09 16:38:06.358 DEBUG 20016 --- [nio-8080-exec-2] .w.s.m.m.a.ServletInvocableHandlerMethod : Could not resolve parameter [0] in public java.lang.String com.example.spring.modular.ReceiveParamController.requestParam1(java.lang.String): Required String parameter 'id' is not present 2020-09-09 16:38:06.358 WARN 20016 --- [nio-8080-exec-2] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.bind.MissingServletRequestParameterException: Required String parameter 'id' is not present] 2020-09-09 16:38:06.358 TRACE 20016 --- [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : No view rendering, null ModelAndView returned. 2020-09-09 16:38:06.358 DEBUG 20016 --- [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : Completed 400 BAD_REQUEST, headers={} 2020-09-09 16:38:06.359 TRACE 20016 --- [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : "ERROR" dispatch for GET "/error", parameters={}, headers={masked} in DispatcherServlet 'dispatcherServlet' 2020-09-09 16:38:06.359 TRACE 20016 --- [nio-8080-exec-2] s.w.s.m.m.a.RequestMappingHandlerMapping : 2 matching mappings: [{ /error}, { /error, produces [text/html]}] 2020-09-09 16:38:06.359 TRACE 20016 --- [nio-8080-exec-2] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController#error(HttpServletRequest) 2020-09-09 16:38:06.359 TRACE 20016 --- [nio-8080-exec-2] .w.s.m.m.a.ServletInvocableHandlerMethod : Arguments: [org.apache.catalina.core.ApplicationHttpRequest@6d9b77a9] 2020-09-09 16:38:06.359 DEBUG 20016 --- [nio-8080-exec-2] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Using 'application/json', given [*/*] and supported [application/json, application/*+json, application/json, application/*+json] 2020-09-09 16:38:06.359 TRACE 20016 --- [nio-8080-exec-2] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Writing [{timestamp=Wed Sep 09 16:38:06 CST 2020, status=400, error=Bad Request, message=, path=/requestParam}] 2020-09-09 16:38:06.360 TRACE 20016 --- [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : No view rendering, null ModelAndView returned. 2020-09-09 16:38:06.360 DEBUG 20016 --- [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : Exiting from "ERROR" dispatch, status 400, headers={masked}
可知测试6没进入断点是因为这个请求被required=true挡掉了,也就是说:@RequestParam无法获取get请求的x-www-form-urlencoded参数,在对比一下测试6和测试7的postman发送请求的详细信息,可以猜想到对于get请求情况下的x-www-form-urlencoded请求参数肯定是被spring处理了,导致接受不到参数。
由测试9可得,@RequestParam当不指定具体参数时,可以使用map接受form-data,x-www-form-urlencoded以及普通的query string参数,也不可映射成list(如果是多个相同的key)。
由测试10可知,@RequestParam当不指定具体参数时,不可将前端多个相同key的参数映射成一个List集合。
由测试11可知,@RequestParam不可映射简单对象User。
该注解常用来处理Content-Type为application/json,application/xml等编码的内容。它是通过使用HandlerAdapter配置的HttpMessageConverters来解析post data body,然后绑定到相应的bean上。因为配置有FormHttpMessageConverter,所以也可以用来处理application/x-www-form-urlencoded的内容,处理完的结果放在一个MultiValueMap<String,String>里,这种情况在某些特殊情况下使用,详情查看FormHttpMessageConverter api;
@Target({ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface RequestBody { boolean required() default true; }
@RequestBody的语法相对简单,直接在形参前加即可。
package com.example.spring.modular; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*; import javax.servlet.http.HttpServletRequest; import javax.websocket.server.PathParam; import java.util.List; import java.util.Map; @Controller public class ReceiveParamController { @RequestMapping(value = "/requestBody") public String requestBody(@RequestBody String id) { System.out.println(id); // 断点1 return "/hello.html"; } @RequestMapping(value = "/requestBodyMap") public String requestBodyMap(@RequestBody Map<String, Object> map) { System.out.println(map); // 断点2 return "/hello.html"; } @RequestMapping(value = "/requestBodyList") public String requestBodyList(@RequestBody List<Object> list) { System.out.println(list); // 断点3 return "/hello.html"; } @RequestMapping(value = "/requestBodyObject") public String requestBodyObject(@RequestBody User user) { System.out.println(user); // 断点4 return "/hello.html"; } @RequestMapping(value = "/requestBodyAndParamsList") public String requestBodyAndParamsList(@RequestBody User user, @RequestParam List<String> id) { System.out.println(user); // 断点5 System.out.println(id); return "/hello.html"; } }
开头介绍已经说明,基于@RequestBody的测试开始之前,我们先做一个预测试,分别在postman中使用Params下Query Params、Body下form-data添加key/value为id=999测试断点1所在方法,发现均不进入断点。
postman下,get请求http://localhost:8080/requestBody,选择Body下的x-www-form-urlencoded,填入参数key/value为id/999,send。
进入断点1位置,注意:此刻参数id不是"999"而是"id=999"
curl --location --request GET 'http://localhost:8080/requestBody' --header 'Content-Type: application/x-www-form-urlencoded' --data-urlencode 'id=999'
postman下,get请求http://localhost:8080/requestBody,选择Body下的raw,下拉框选JSON,填入{"id":"999"},send。
进入断点1位置,此刻参数id为:"{ "id":999 }"
postman下,get请求http://localhost:8080/requestBodyMap,选择Body下的x-www-form-urlencoded,填入参数key/value为id/999,send。
不进入断点2位置。后端提示Required request body is missing,400 Bad request。
postman下,get请求http://localhost:8080/requestBodyMap,选择Body下的raw,下拉框选JSON,填入{"id":"999"},send。
进入断点2位置,map的size为1,map.get("id")的value值为"999"
postman下,get请求http://localhost:8080/requestBodyList,只有选择Body下的raw,下拉框选JSON,填入["a","b"],send,进入断点3位置。其他情况无论选择哪一种方式都不能进入断点3,说明@RequestBody仅可接受application/json类型的List数据。
curl --location --request GET 'http://localhost:8080/requestBodyList' --header 'Content-Type: application/json' --data-raw '["id","key"]'
postman下,get请求http://localhost:8080/requestBodyObject,选择Body下的x-www-form-urlencoded,填入参数key/value为id/999,send。
不进入断点4位置。后端提示Required request body is missing,400 Bad request。
curl --location --request GET 'http://localhost:8080/requestBodyObject' --header 'Content-Type: application/x-www-form-urlencoded' --data-urlencode 'id=999' --data-urlencode 'friends=a'
postman下,get请求http://localhost:8080/requestBodyObject,选择Body下的raw,下拉框选JSON,填入{"id":"999","friends":["a","b"]},send。
进入断点4位置,user.getId()的值为"999",friends为一个不为空的ArrayList。
curl --location --request GET 'http://localhost:8080/requestBodyObject' --header 'Content-Type: application/json' --data-raw '{"id":"999","friends":["a","b"]}'
在@RequestParam测试的时候得知,该注解不可映射List参数,现在当它和@RequestBody组合使用时。结合测试5、6经验,选择Body下的raw,下拉框选JSON,填入{"id":"999","friends":["a","b"]},然后在Params下的Query Params里填入id/888,id/999,然后send。进入断点5,此刻user被完全映射,集合List的id也包含两个元素888和999。由此可知当@RequestBody和@RequestParam组合使用时,@RequestParam可以接受List参数。
curl --location --request GET 'http://localhost:8080/requestBodyAndParamsList' --header 'Content-Type: application/json' --data-raw '{"id":"999","friends":["a","b"]}'
由预测试和测试0和测试1可知,@RequestBody仅可接收x-www-form-urlencoded和application/json类型的数据,且映射参数的方式都是以map方式映射,如果形参使用字符串接收,则变为json字符串。
当形参使用map接收时,要注意传递的参数格式,由于会进行泛型这一点也可能导致你匹配不上。由测试2、3可知,map接受是只能接受application/json类型的数据。
由测试4可知,@RequestBody仅可接受applicationjson传递的List数据。
补充知识点:一个请求只能有一个@RequestBody但是可以有多个@RequestParam,
当@RequestBody和@RequestParam同时使用时:@RequestParam指定的元素除了它自己可以映射的普通元素、map外,变得可以映射集合List或数组。但注意它依旧不能映射对象User。
@Target({ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface PathVariable { @AliasFor("name") String value() default ""; @AliasFor("value") String name() default ""; boolean required() default true; }
name即value,required(默认为true)这些和@RequestParam类似,但是它没有defaultValue。
@PathVariable(value = "id", required = true) # 配合@RequestMapping("/hello/{id}")
通过@PathVariable可以将URL中占位符参数绑定到处理器类的方法的形参中(如果Controller上的RequestMapping也有占位符,也可以取到)。要求@PathVariable中value指定的值必须可url路径参数名称对应才能映射。
package com.example.spring.modular; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*; import javax.servlet.http.HttpServletRequest; import javax.websocket.server.PathParam; import java.util.List; import java.util.Map; @Controller @RequestMapping("receive/{name}") public class ReceiveParamController { @RequestMapping("/pathVariable/{id}") public String pathVariable(@PathVariable("name") String name, @PathVariable(value = "id", required = true) String id) { System.out.println(id); return "/hello.html"; } }
关于@PathVariable中required设置为false的问题:
当某个形参的required设置为false是,你要多写一个映射路径,因为当这个参数不必要时那映射路径肯定是不一样的,例如:
package com.example.spring.modular; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*; import javax.servlet.http.HttpServletRequest; import javax.websocket.server.PathParam; import java.util.List; import java.util.Map; @Controller public class ReceiveParamController { @RequestMapping(value = {"/pathVariable/{id}/{name}", "/pathVariable/{name}"}) public String pathVariable(@PathVariable(value = "id", required = false) String id, @PathVariable("name") String name) { System.out.println(id); System.out.println(name); return "/hello.html"; } }
get请求地址为:http://localhost:8080/pathVariable/jack,请求打印id为null,name=jack
注意@PathParam不是Spring MVC用于获取request请求参数的注解,不过它是JavaEE 6引入的新技术,JAX-RS即Java API for RESTful Web Services,该技术常用注解包括@Path、@GET、@POST、@PathParam、@QueryParam等。具体参见1、参见2,在这里不详细研究。