目录
3.2 参数解析器-HandlerMethodArgumentResolver
3.3 返回值处理器 HandlerMethodReturnValueHandler
@RequestMapping注解可以用下面几个注解代替
@Getmapping @PostMapping @DeleteMapping @PutMapping
分别代表 获取、保存、删除、修改的请求类型。url采用路径式的风格。

使用RestFul风格提交请求时,要先在配置文件中开启Rest功能,然后表单要加上一个隐藏域,表明请求方式。
application.yml:
- spring:
- mvc:
- hiddenmethod:
- filter:
- enabled: true #开启页面表单的Rest功能
index.html:
- <div>
- <form action="/user" method="get">
- <input value="Rest-GET" type="submit">
- form>
-
- <form action="/user" method="post">
- <input value="Rest-POST" type="submit">
- form>
-
- <form action="/user" method="post">
- <input type="hidden" name="_method" value="put">
- <input value="Rest-PUT" type="submit">
- form>
-
- <form action="/user" method="post">
- <input type="hidden" name="_method" value="delete">
- <input value="Rest-DELETE" type="submit">
- form>
- div>
controller:
- @RestController
- public class testRESTful {
- @GetMapping("/user")
- public String testGet(){
- return "get user";
- }
-
- @PostMapping("/user")
- public String testPost(){
- return "post user";
- }
-
- @PutMapping("/user")
- public String testPut(){
- return "put user";
- }
-
- @DeleteMapping("/user")
- public String testDelete(){
- return "delete user";
- }
- }
Rest原理(表单提交要使用REST的时候)
过滤器的HttpMethodRequestWrapper重写了getMethod方法,封装了_method的值,这里用到了包装模式,返回的是我们想要的请求类型。
过滤器链放行的时候用wrapper,以后的方法调用getMethod是调用HttpMethodRequestWrapper的,就是包装后的,而不是原生的。这样就可以使用PUT、DELTETE请求啦。
doGet(HttpServlet)->processRequest(HttpServletBean)->
doService(FramworkServlet)->doDispatch(DispatchServlet)
- protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
- HttpServletRequest processedRequest = request;
- HandlerExecutionChain mappedHandler = null;
- boolean multipartRequestParsed = false;
-
- WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
-
- try {
- ModelAndView mv = null;
- Exception dispatchException = null;
-
- try {
- processedRequest = checkMultipart(request);
- multipartRequestParsed = (processedRequest != request);
-
- // 找到当前请求使用哪个Handler(Controller的方法)处理
- mappedHandler = getHandler(processedRequest);
- ... ...

@PathVariable 将Rest风格路径中动态的参数映射到处理器方法的形参中
@RequestHeader 请求头信息和控制器方法形参创建映射关系
@ModelAttribute
@RequestParam 请求参数和控制器方法形参创建映射关系
@MatrixVariable 获取矩阵变量
@CookieValue cookie数据和控制器方法形参创建映射关系
@RequestBody 获取请求体
@ResponseBody 标注方法,返回值作为响应体
@RequestAttritube 获取request域属性(页面跳转时取域数据)
<a href="/car/1/owner/wz">@PathVariablea>
- @RestController
- public class testParam {
- @GetMapping("/car/{id}/owner/{username}")
- public Map
getCar(@PathVariable("id") Integer id,@PathVariable Map vars) { - Map
map=new HashMap<>(); - map.put("id",id);
- map.put("vars",vars);
- return map;
- }
- }
<a href="/car/1/owner/wz">@RequestHeadera>
- @RestController
- public class testParam {
- @GetMapping("/car/{id}/owner/{username}")
- public Map
getCar(@RequestHeader Map headers,@RequestHeader("User-Agent") String header) { - Map
map=new HashMap<>(); - map.put("header",header);//某个key 对应的 value
- map.put("headers",headers);//所有key 对应的 value
- return map;
- }
- }
-
-
- 结果:
- {"headers":{"host":"localhost:8080","connection":"keep-alive","sec-ch-ua":"\" Not;A Brand\";v=\"99\", \"Microsoft Edge\";v=\"103\", \"Chromium\";v=\"103\"","sec-ch-ua-mobile":"?0","sec-ch-ua-platform":"\"Windows\"","upgrade-insecure-requests":"1","user-agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.134 Safari/537.36 Edg/103.0.1264.77","accept":"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9","sec-fetch-site":"same-origin","sec-fetch-mode":"navigate","sec-fetch-user":"?1","sec-fetch-dest":"document","referer":"http://localhost:8080/index.html","accept-encoding":"gzip, deflate, br","accept-language":"zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6","cookie":"Idea-78910da9=e89c6d10-8e05-4fd7-8e94-8d41c1b52e21"},"header":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.134 Safari/537.36 Edg/103.0.1264.77"}
<a href="/car/1/owner/ikun?age=18&interest=rap">@RequestParama>
- @RestController
- public class testParam {
- @GetMapping("/car/{id}/owner/{username}")
- public Map
getCar(@RequestParam("age") Integer age,@RequestParam Map params) { - Map
map=new HashMap<>(); - map.put("age",age);
- map.put("params",params);
- return map;
- }
- }
-
-
- 结果:
- {"params":{"age":"18","interest":"rap"},"age":18}
<a href="/car/1/owner/ikun?age=18&interest=rap">@CookieValuea>
- @RestController
- public class testParam {
- @GetMapping("/car/{id}/owner/{username}")
- public Map
getCar(@CookieValue("Idea-78910da9") String c){ - Map
map=new HashMap<>(); - map.put("Idea-78910da9",c);
- return map;
- }
- }
-
-
- 结果:
- {"Idea-78910da9":"e89c6d10-8e05-4fd7-8e94-8d41c1b52e21"}
- <form action="/car" method="post">
- 汽车品牌:<input name="brand"/>
- 汽车型号:<input name="model"/>
- <input type="submit" value="提交">
- form>
- @RestController
- public class testParam {
- @PostMapping("/car")
- public Map
getCar(@RequestBody String body){ - Map
map=new HashMap<>(); - map.put("RequestBody",body);
- return map;
- }
-
- }
-
-
- 结果:
- {"RequestBody":"brand=Toyota & model=Camary"}
矩阵变量请求是一种新的请求风格,严格来说矩阵变量的请求需要用到rest风格但是又不同于rest.
- //queryString请求方式
- /cars?brand=audi&model=A6&price=500000
-
- //REST请求
- /cars/audi/A6/500000
-
- //MatrixVariable矩阵变量
- /cars;brand=audi;model=A6;price=500000
这个功能默认是关闭的,要在配置类中手动开启:
- @Configuration
- public class myConfig implements WebMvcConfigurer {
- @Override
- public void configurePathMatch(PathMatchConfigurer configurer) {
- UrlPathHelper urlPathHelper = new UrlPathHelper();
- urlPathHelper.setRemoveSemicolonContent(false);//矩阵变量功能生效
- configurer.setUrlPathHelper(urlPathHelper);
- }
- }
下面是遇到一个path时的情况:
<a href="/cars/sell;brand=audi;model=A6;price=500000,4000000">@MatrixVariablea>
- @GetMapping("/cars/{path}")
- public Map carsSell(@MatrixVariable("brand") String brand,
- @MatrixVariable("model") String model,
- @MatrixVariable("price") List
price, - @PathVariable("path") String path){
- Map
map = new HashMap<>(); - map.put("brand",brand);
- map.put("model",model);
- map.put("price",price);
- map.put("path",path);
- return map;
- }
-
-
- 结果:
- {"path":"sell","price":["500000","4000000"],"model":"A6","brand":"audi"}
下面是遇到多个path时的情况:
<a href="/cars/1;brand=audi/2;brand=benz">@MatrixVariablea>
- @GetMapping("/cars/{id1}/{id2}")
- public Map carBrand(@MatrixVariable(value = "brand", pathVar = "id1") String id1brand,
- @MatrixVariable(value = "brand", pathVar = "id2") String id2brand){
- Map
map = new HashMap<>(); - map.put("id1brand",id1brand);
- map.put("id2brand",id2brand);
- return map;
- }
-
-
- 结果:
- {"id1brand":"audi","id2brand":"benz"}
WebRequest、ServletRequest、MultipartRequest、 HttpSession、javax.servlet.http.PushBuilder、Principal、InputStream、Reader、HttpMethod、Locale、TimeZone、ZoneId
如何获取到这些参数呢?
是ServletRequestMethodArgumentResolver 帮我们解析以上部分参数的。
如Map、Model(map、model里面的数据会被放在request的请求域)、Errors/BindingResult、RedirectAttributes( 重定向携带数据)、ServletResponse(response)、SessionStatus、UriComponentsBuilder、ServletUriComponentsBuilder
Map、Model类型的参数,会返回 mavContainer.getModel();---> BindingAwareModelMap 是ModeMap类型的,最终都会在DispatcherServlet中封装成一个ModelAndView。
如何使用已经在SpringMVC中介绍了。
就是自动将表单提交的内容与javaBean的属性相对应。可以自动类型转换与格式化,可以级联封装。
- /**
- * 姓名: <input name="userName"/> <br/>
- * 年龄: <input name="age"/> <br/>
- * 生日: <input name="birth"/> <br/>
- * 宠物姓名:<input name="pet.name"/><br/>
- * 宠物年龄:<input name="pet.age"/>
- */
- @Data
- public class Person {
-
- private String userName;
- private Integer age;
- private Date birth;
- private Pet pet;
-
- }
-
- @Data
- public class Pet {
-
- private String name;
- private String age;
-
- }
- // Actually invoke the handler.
- //DispatcherServlet -- doDispatch
- mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
- mav = invokeHandlerMethod(request, response, handlerMethod); //执行目标方法
- //ServletInvocableHandlerMethod
- Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
- //获取方法的参数值
- Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
确定将要执行的目标方法的每一个参数的值是什么;SpringMVC目标方法能写多少种参数类型。取决于参数解析器

解析器挨个判断是否支持解析这种参数,如果支持就调用 resolveArgument 方法解析
- @Nullable
- private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
- HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
- if (result == null) {
- for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
- if (resolver.supportsParameter(parameter)) {
- result = resolver;
- this.argumentResolverCache.put(parameter, result);
- break;
- }
- }
- }
- return result;
- }


这个处理器会根据返回值类型去调用对应的处理类进行处理。
| 返回值类型 | 处理类 |
|---|---|
| String(方法上无ResponseBody注解) | ViewNameMethodReturnValueHandler |
| View | ViewMethodReturnValueHandler |
| ModelAndView | ModelAndViewMethodReturnValueHandle |
| Model | ModelMethodProcessor |
| Map | MapMethodProcessor |
| HttpHeaders | HttpHeadersReturnValueHandler |
| ModelAttribute(或者是自定义对象) | ModelAttributeMethodProcessor |
| HttpEntity | HttpEntityMethodProcessor |
| ResponseEntity | ResponseBodyEmitterReturnValueHandler |
| ResponseBody注解 | RequestResponseBodyMethodProcessor |
由ServletModelAttributeMethodProcessor 参数处理器处理,它支持自定义参数绑定,在底层使用了WebDataBinder数据绑定器,而数据绑定器中有一个conversionService,conversionService中注册了很多converters,converters会帮助我们进行类型转换
将所有的数据都放在 ModelAndViewContainer;包含要去的页面地址View,还包含Model数据。接下来还是封装成 ModelAndView,最后处理派发结果,将数据放到请求域中。