使用Spring MVC
进行Web
应用开发时,请求参数接收是很重要的一个方面。一般情况下,我们可以通过request.getParameter(name)
这种方式来获取指定名称参数的值,这种方式是由Servlet
容器自身提供的。但使用这种方式会导致开发人员必须在控制器方法中书写仅用于接收参数的逻辑,在参数量多的情况下,这部分代码甚至占据了方法体的大部分,而核心业务逻辑反而淹没其中。为了使开发人员从这种窘境中解脱出来提高生产效率,关注核心业务,Spring MVC
提供了各种便利工具来接收参数,同时也将该关注点从主业务逻辑中分离出来。
Spring MVC
所提供的这些工具,有@PathVariable
,@RequestParam
,@RequestBody
,@RequestParam
,@CookieValue
等注解,也有对一些类直接支持逻辑。本文不讲这些注解或者被支持的类如何使用以及它们的目的,而是看看针对这些便利工具,Spring MVC
在背后所提供的参数解析器:HandlerMethodArgumentResolver
。
下表是Spring MVC
所提供的参数解析器实现类:
控制器方法参数解析器 | 所能解析的参数的类型特征 |
---|---|
RequestParamMethodArgumentResolver |
@RequestParam 并且参数类型不是Map
|
RequestParamMapMethodArgumentResolver |
@RequestParam 并且参数类型是Map
|
PathVariableMethodArgumentResolver |
@PathVariable 并且参数类型不是Map
|
PathVariableMapMethodArgumentResolver |
@PathVariable 并且参数类型是Map
|
MatrixVariableMethodArgumentResolver |
@MatrixVariable 并且参数类型不是Map
|
MatrixVariableMapMethodArgumentResolver |
@MatrixVariable 并且参数类型是Map
|
ServletModelAttributeMethodProcessor |
@ModelAttribute ,Servlet 相关 |
RequestResponseBodyMethodProcessor |
@RequestBody ,@ResponseBody
|
RequestPartMethodArgumentResolver |
@RequestPart |
RequestHeaderMethodArgumentResolver |
@RequestHeader 并且参数类型不是Map
|
RequestHeaderMapMethodArgumentResolver |
@RequestHeader 并且参数类型是Map
|
ServletCookieValueMethodArgumentResolver |
@CookieValue |
ExpressionValueMethodArgumentResolver |
@Value |
SessionAttributeMethodArgumentResolver |
@SessionAttribute |
RequestAttributeMethodArgumentResolver |
RequestAttribute |
ServletRequestMethodArgumentResolver |
WebRequest ,ServletRequest ,MultipartRequest ,HttpSession ,PushBuilder ,Principal ,InputStream ,Reader ,HttpMethod ,Locale ,TimeZone ,java.time.ZoneId
|
ServletResponseMethodArgumentResolver |
ServletResponse ,OutputStream ,Writer
|
HttpEntityMethodProcessor |
HttpEntity ,RequestEntity ,ResponseEntity
|
RedirectAttributesMethodArgumentResolver |
RedirectAttributes |
ModelMethodProcessor |
Model 类型的参数和返回值 |
MapMethodProcessor |
Map 类型的参数和返回值 |
ErrorsMethodArgumentResolver |
Errors |
SessionStatusMethodArgumentResolver |
SessionStatus |
UriComponentsBuilderMethodArgumentResolver |
UriComponentsBuilder |
Spring MVC
应用缺省使用类型为RequestMappingHandlerAdapter
的bean
组件requestMappingHandlerAdapter
调用每个请求对应的控制器方法。在bean
组件requestMappingHandlerAdapter
初始化时,它会构建一个HandlerMethodArgumentResolverComposite
对象,该对象是多个HandlerMethodArgumentResolver
的一个集合:
// RequestMappingHandlerAdapter 代码片段
// RequestMappingHandlerAdapter 的初始化方法
@Override
public void afterPropertiesSet() {
// Do this first, it may add ResponseBody advice beans
initControllerAdviceCache();
if (this.argumentResolvers == null) {
// 获取缺省的 HandlerMethodArgumentResolver, 多个
List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
// 构造 HandlerMethodArgumentResolverComposite 对象,
// 包装缺省的多个 HandlerMethodArgumentResolver
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);
}
}
从上面的代码可见,缺省使用的控制器方法参数解析器由getDefaultInitBinderArgumentResolvers()
提供。该方法实现如下:
// RequestMappingHandlerAdapter 代码片段
private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>();
// 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 , 如果上面的参数解析器都处理不了,尝试使用下面的解析器尝试兜底
// true 表示 : 即使不使用注解 @RequestParam ,简单类型的方法参数也会被认为是请求参数被解析,
// 此时请求参数名称会根据方法参数名称派生而来。
resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));
// true 表示 : 即使不使用注解 @ModelAttribute,非简单类型的方法参数和返回值也会被认为
// 是 model attribute
resolvers.add(new ServletModelAttributeMethodProcessor(true));
return resolvers;
}
上面方法中参数解析器添加的顺序很重要,因为为每个参数寻找参数解析器时,会按照以上添加顺序去寻找第一个能够解析该参数的参数解析器。