正文
从SpringMVC的执行流程来看,RequestMappingHandlerAdapter这个HandlerAdapter负责执行Controller中的相应方法,并对请求参数进行处理。
1、参数处理器HandlerMethodArgumentResolver的加载
RequestMappingHandlerAdapter实现了InitializingBean接口,因此在SpringBoot启动过程中实例化RequestMappingHandlerAdapter过程中(执行init-method()方法之前),会调用RequestMappingHandlerAdapter#afterPropertiesSet()方法:
@Override public void afterPropertiesSet() { // Do this first, it may add ResponseBody advice beans initControllerAdviceCache();
if (this.argumentResolvers == null) {
List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
}
if (this.initBinderArgumentResolvers == null) {
List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();
this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
}
if (this.returnValueHandlers == null) {
List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
}
《门面》HandlerMethodArgumentResolverComposite
RequestMappingHandlerAdapter中的参数解析器变量argumentResolvers的类型是 HandlerMethodArgumentResolverComposite;HandlerMethodArgumentResolverComposite又组合了所有的HandlerMethodArgumentResolver;
因此可以将HandlerMethodArgumentResolverComposite看做是所有参数解析器的“门面”,但凡需要用到参数解析器都都将请求先打到HandlerMethodArgumentResolverComposite。
- 这个设计和SpringBoot的事件监听器
EventPublishingRunListener
异曲同工。
getDefaultArgumentResolvers()方法获取所有的参数解析器HandlerMethodArgumentResolver
时就是单纯的new,然后放到List中。
- 当我们自定义了参数解析器时,同样也会被加载到。
获取到所有的HandlerMethodArgumentResolver之后,直接new一个HandlerMethodArgumentResolverComposite,然后将所有的HandlerMethodArgumentResolver都添加到HandlerMethodArgumentResolverComposite中。
2)参数解析器HandlerMethodArgumentResolver的分类
从RequestMappingHandlerAdapter#getDefaultArgumentResolvers()方法获取所有的参数解析器HandlerMethodArgumentResolver中注释来看,Spring MVC中将参数解析器分为四大类:
- 基于注解 解析参数,比如:接口方法参数中的@RequestParam、@PathVariable、@RequestBody等注解。
- 基于类型 解析参数,比如:接口方法参数是HttpServletRequest、HttpServletResponse等。
- 自定义参数解析,比如:接口方法参数上有自定义注解,则可在实例化RequestMappingHandlerAdapter时 将自定义的HandlerMethodArgumentResolver接口实现类设置到 customArgumentResolvers 属性中。
- 兜底的参数解析,当上述三类参数解析器都无法解析参数时,此类参数解析器会解析。比如最简单的GET请求:http://localhost:8080/trace/test?name=saint
参数解析器的应用顺序实际就是越晚添加到List集合中,就越最后被遍历到。
日常开发过程中一般不需要自定义参数解析器,就可以覆盖90%以上的开发需求。下一篇文章输出自定义参数解析器。
2、参数处理器的执行时机
我们知道请求是交给HandlerAdapter执行的,对于普通的HTTP请求也就是交给RequestMappingHandlerAdapter执行,RequestMappingHandlerAdapter要执行某个方法时,会将RequestMappingHandlerAdapter中的参数解析器“门面”绑定给方法。
继续往下跟,到真正解析前的代码路径如下:
InvocableHandlerMethod#getMethodArgumentValues()方法会应用相应的参数解析器从请求中解析出方法的参数值。
根据方法参数标注的注解、参数类型会选择不同的参数解析器HandlerMethodArgumentResolver。有种策略模式的意思。
参数解析器的选择
选择参数解析器HandlerMethodArgumentResolver时,有一层小优化,针对每个方法的参数对应的参数解析器都会做一个缓存。如果从缓存中找不到方法参数对应的参数解析器,则遍历所有的参数解析器(默认26个),调用参数解析器的supportsParameter()方法判断参数解析器是否支持当前参数的解析,如果支持则直接返回。
下面以不同的入参形式暂开具体的讨论。
3、处理注解类型的参数
1)处理@RequestParam标注的参数
@RequestParam常用于GET请求,注解里的value或name互为别名、对应请求的参数名,required()属性表示参数是否必须存在。
分析以如下请求为例:
这里特意将方法的参数名和@RequestParam中的value设置为不一样的值。
RequestParamMethodArgumentResolver负责解析被@RequestParam标注的参数。
该实现类仅支持处理被@RequestParam注解标注的参数(参数如果是Map类型,@RequestParam注解里的name/value必须有值,否则不知道应该拿哪个KV键值对)。
如果参数没有被@RequestParam注解标注,走进的逻辑是兜底的参数解析逻辑。
解析参数
解析参数会走入到HandlerMethodArgumentResolver#resolveArgument()方法;
Spring MVC设计了一个抽象父类AbstractNamedValueMethodArgumentResolver,用于给参数解析器提供一种通用的模板实现,子类通过重写模板中的某个方法实现自己的逻辑。
RequestParamMethodArgumentResolver亦是AbstractNamedValueMethodArgumentResolver的子类,所以执行RequestParamMethodArgumentResolver#resolveArgument()实际会进入到: