四、理解 @PathVariable
说到这了,顺便说一下 @PathVariable 注解,其用来获取请求路径(url )中的动态参数。如果@RequestMapping中表示为”item/{id}”,id和形参名称一致,@PathVariable不用指定名称。
页面发出请求:
function login() { var url = "${pageContext.request.contextPath}/person/login/"; var query = $('#id').val() + '/' + $('#name').val() + '/' + $('#status').val(); url += query; $.get(url, function(data) { alert("id: " + data.id + "name: " + data.name + "status: " + data.status); }); }
/** * @RequestMapping(value = "user/login/{id}/{name}/{status}") 中的 {id}/{name}/{status} * 与 @PathVariable int id、@PathVariable String name、@PathVariable boolean status * 一一对应,按名匹配。 */ @RequestMapping(value = "user/login/{id}/{name}/{status}") @ResponseBody //@PathVariable注解下的数据类型均可用 public User login(@PathVariable int id, @PathVariable String name, @PathVariable boolean status) { //返回一个User对象响应ajax的请求 return new User(id, name, status); }
五、理解 HandlerMethodArgumentResolver
在初学springmvc框架时,我就一直有一个疑问,为什么controller方法上竟然可以放这么多的参数,而且都能得到想要的对象,比如HttpServletRequest或HttpServletResponse,各种注解@RequestParam、@RequestHeader、@RequestBody、@PathVariable、@ModelAttribute等。
org.springframework.web.method.support.HandlerMethodArgumentResolver接口
springmvc自带的一些实现:
ServletRequestMethodArgumentResolver和ServletResponseMethodArgumentResolver处理了自动绑定HttpServletRequest和HttpServletResponse
RequestParamMapMethodArgumentResolver处理了@RequestParam
RequestHeaderMapMethodArgumentResolver处理了@RequestHeader
PathVariableMapMethodArgumentResolver处理了@PathVariable
ModelAttributeMethodProcessor处理了@ModelAttribute
RequestResponseBodyMethodProcessor处理了@RequestBody
……
springmvc具有一个参数解析器容器RequestMappingHandlerAdapter.argumentResolvers,该参数的初始化在RequestMappingHandlerAdapter#afterPropertiesSet()。
public void afterPropertiesSet() { ...... if (this.argumentResolvers == null) { List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers(); this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers); } ...... } /** * Return the list of argument resolvers to use including built-in resolvers * and custom resolvers provided via {@link #setCustomArgumentResolvers}. */ private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() { List<HandlerMethodArgumentResolver> resolvers = new ArrayList<HandlerMethodArgumentResolver>(); // Annotation-based argument resolution resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false)); resolvers.add(new RequestParamMapMethodArgumentResolver()); resolvers.add(new PathVariableMethodArgumentResolver()); resolvers.add(new PathVariableMapMethodArgumentResolver()); resolvers.add(new MatrixVariableMethodArgumentResolver()); resolvers.add(new MatrixVariableMapMethodArgumentResolver()); resolvers.add(new ServletModelAttributeMethodProcessor(false)); resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice)); resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters(), this.requestResponseBodyAdvice)); resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory())); resolvers.add(new RequestHeaderMapMethodArgumentResolver()); resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory())); resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory())); resolvers.add(new SessionAttributeMethodArgumentResolver()); resolvers.add(new RequestAttributeMethodArgumentResolver()); // Type-based argument resolution resolvers.add(new ServletRequestMethodArgumentResolver()); resolvers.add(new ServletResponseMethodArgumentResolver()); resolvers.add(new HttpEntityMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice)); resolvers.add(new RedirectAttributesMethodArgumentResolver()); resolvers.add(new ModelMethodProcessor()); resolvers.add(new MapMethodProcessor()); resolvers.add(new ErrorsMethodArgumentResolver()); resolvers.add(new SessionStatusMethodArgumentResolver()); resolvers.add(new UriComponentsBuilderMethodArgumentResolver()); // Custom arguments if (getCustomArgumentResolvers() != null) { resolvers.addAll(getCustomArgumentResolvers()); } // Catch-all resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true)); resolvers.add(new ServletModelAttributeMethodProcessor(true)); return resolvers; }
可以看出springmvc的参数解析器容器中存放着内置的参数解析器 + 自定义解析器,这里边就包括@RequestBody的解析器RequestResponseBodyMethodProcessor,来看一下这个解析器的主要方法:
@Override public boolean supportsParameter(MethodParameter parameter) { return parameter.hasParameterAnnotation(RequestBody.class); } @Override public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { // 这里使用MappingJackson2HttpMessageConverter将输入流body体中的转化为Book对象 }
相关API:
作用:
SpringMVC解析器用于解析request请求参数并绑定数据到Controller的入参上。
自定义一个参数解析器需要实现HandlerMethodArgumentResolver接口,重写supportsParameter和resolveArgument方法,配置文件中加入resolver配置。
如果需要多个解析器同时生效需要在一个解析器中对其他解析器做兼容。
由来:
如果前端对某个不要入参做了加密操作,后端接收到该参数后,都需要进行解密操作。
针对这种需求,首先想到的是filter或者interceptor实现,但是由于HttpServletRequest对象本身是不提供setParameter()方法的,因此想要修改request中的参数值为decode后的值是不易达到的。
SpringMVC的HandlerMethodArgumentResolver,解析器;其功能就是解析request请求参数并绑定数据到Controller的入参上。
注意:
1、一个参数解析器最重要的方法有两个:
(1)supportsParameter 指定哪些参数使用该解析器进行解析,用于判定是否需要处理该参数分解,返回true为需要,并会去调用下面的方法resolveArgument。
(2)resolveArgument 对参数进行真正的解析操作,真正用于处理参数分解的方法,返回的Object就是controller方法上的形参对象。
这也是自定义参数解析器需要去实现的两个方法。
2、在解析器容器中,自定义解析器是位于内置解析器之后,这个顺序也是解析器的优先级,也就是说假设有一个参数同时满足两个解析器,只有第一个解析器会生效,那么怎么去调整这个解析器的顺序呢?
好,现在,我们已经大致了解了springmvc的参数解析器,以及@RequestBody的解析过程。那么来看一下这个例子:
@RequestMapping(value = "/two-body", method = RequestMethod.POST) public Book testCommon(@RequestBody Book book1, @RequestBody Book book2) { Book book = new Book(); book.setId(Optional.ofNullable(book1).orElse(book2).getId()); book.setName(Optional.ofNullable(book1).orElse(book2).getName()); return book; }