Spring MVC多种请求入参处理方式都在这了

69 阅读4分钟

正文

从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

image.png

RequestMappingHandlerAdapter中的参数解析器变量argumentResolvers的类型是 HandlerMethodArgumentResolverComposite;HandlerMethodArgumentResolverComposite又组合了所有的HandlerMethodArgumentResolver;

image.png 因此可以将HandlerMethodArgumentResolverComposite看做是所有参数解析器的“门面”,但凡需要用到参数解析器都都将请求先打到HandlerMethodArgumentResolverComposite。

  • 这个设计和SpringBoot的事件监听器EventPublishingRunListener 异曲同工。

image.png

image.png

getDefaultArgumentResolvers()方法获取所有的参数解析器HandlerMethodArgumentResolver时就是单纯的new,然后放到List中。

  • 当我们自定义了参数解析器时,同样也会被加载到。

image.png

获取到所有的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

image.png

参数解析器的应用顺序实际就是越晚添加到List集合中,就越最后被遍历到。

日常开发过程中一般不需要自定义参数解析器,就可以覆盖90%以上的开发需求。下一篇文章输出自定义参数解析器。

2、参数处理器的执行时机

我们知道请求是交给HandlerAdapter执行的,对于普通的HTTP请求也就是交给RequestMappingHandlerAdapter执行,RequestMappingHandlerAdapter要执行某个方法时,会将RequestMappingHandlerAdapter中的参数解析器“门面”绑定给方法。

image.png

继续往下跟,到真正解析前的代码路径如下:

image.png

image.png

InvocableHandlerMethod#getMethodArgumentValues()方法会应用相应的参数解析器从请求中解析出方法的参数值。

根据方法参数标注的注解、参数类型会选择不同的参数解析器HandlerMethodArgumentResolver。有种策略模式的意思。

参数解析器的选择

选择参数解析器HandlerMethodArgumentResolver时,有一层小优化,针对每个方法的参数对应的参数解析器都会做一个缓存。如果从缓存中找不到方法参数对应的参数解析器,则遍历所有的参数解析器(默认26个),调用参数解析器的supportsParameter()方法判断参数解析器是否支持当前参数的解析,如果支持则直接返回。

image.png

下面以不同的入参形式暂开具体的讨论。

3、处理注解类型的参数

1)处理@RequestParam标注的参数

image.png

@RequestParam常用于GET请求,注解里的value或name互为别名、对应请求的参数名,required()属性表示参数是否必须存在。

分析以如下请求为例:

image.png

这里特意将方法的参数名和@RequestParam中的value设置为不一样的值。

RequestParamMethodArgumentResolver负责解析被@RequestParam标注的参数。

image.png

该实现类仅支持处理被@RequestParam注解标注的参数(参数如果是Map类型,@RequestParam注解里的name/value必须有值,否则不知道应该拿哪个KV键值对)。

如果参数没有被@RequestParam注解标注,走进的逻辑是兜底的参数解析逻辑。

解析参数

image.png

解析参数会走入到HandlerMethodArgumentResolver#resolveArgument()方法;

Spring MVC设计了一个抽象父类AbstractNamedValueMethodArgumentResolver,用于给参数解析器提供一种通用的模板实现,子类通过重写模板中的某个方法实现自己的逻辑。

RequestParamMethodArgumentResolver亦是AbstractNamedValueMethodArgumentResolver的子类,所以执行RequestParamMethodArgumentResolver#resolveArgument()实际会进入到: