Spring-22 SpringMVC HTTP Message 转换 Java 对象
Spring 源码系列文章会遵循由浅入深,由易到难,由宏观到微观的原则,目标是尽量降低学习难度,而不是一上来就迷失在源码当中. 文章会从一个场景作为出发点,针对性的目的性极强的针对该场景对 Spring 的实现原理,源码进行探究学习。该系列文章会让你收获什么? 从对 Spring 的使用者成为 Spring 专家。该文章会同步在微信公众号 【DevXTalk】, 方便在微信客户端阅读。
上篇内容介绍了处理器适配器,它作为处理一次 HTTP
请求的入口,本篇内容进入到处理器适配器内部介绍 SpringMVC
是如何把 HTTP Message
转换成处理器最终能使用的 Java
对象。本篇内容主要针对 RequestMappingHandlerAdapter
处理器适配器来展开, RequestMappingHandlerAdapter
在我们日常工作使用到的频率很高,其他的适配器把 HTTP Message
转换 Java
对象的过程很简单,不需要我们关心。Servlet
容器例如 Tomcat
已经帮我们做了将 HTTP Message
转换为 Servlet API
的工作,在使用层面上我们是不需要关心的。 SpringMVC
在 Servlet API
的基础上做了更丰富的功能支持,可以将 Servlet API
中封装的请求相关信息封装为各种更丰富的 Java
对象。
核心接口 HandlerMethodArgumentResolver
HandlerMethodArgumentResolver
是完成转换过程的核心接口, supportsParameter
方法用来检测这个参数解析器能不能解析这个参数, 处理器方法上的每个参数都会被封装为 MethodParameter
. 如果supportsParameter
方法返回 true
就去调用 resolveArgument
对参数进行解析, 把 Servlet API
解析成处理器方法上的参数需要的数据类型对象。我们可以随意的扩展 HandlerMethodArgumentResolver
接口的实现,理论上来说可以处理各种数据类型都没问题。
java
public interface HandlerMethodArgumentResolver {
boolean supportsParameter(MethodParameter parameter);
@Nullable
Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;
}
RequestMappingHandlerAdapter
默认的参数解析器
如果我们没有特别指定用哪些参数解析器, RequestMappingHandlerAdapter
就会使用默认的一系列参数解析器,相当于出厂配置,开箱即用。这里有 Resolver
结尾的, 也有 Processor
结尾的,Resolver
结尾是只实现了参数解析,Processor
结尾的是同时实现了参数解析和返回值处理。也就是实现了 HandlerMethodArgumentResolver
接口和 HandlerMethodReturnValueHandler
接口。 HandlerMethodReturnValueHandler
接口负责将处理器返回的 Java
对象处理成 HTTP Message
响应给客户端。
java
/**
* 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<>(30);
// 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());
if (KotlinDetector.isKotlinPresent()) {
resolvers.add(new ContinuationHandlerMethodArgumentResolver());
}
// Custom arguments
if (getCustomArgumentResolvers() != null) {
resolvers.addAll(getCustomArgumentResolvers());
}
// Catch-all
resolvers.add(new PrincipalMethodArgumentResolver());
resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));
resolvers.add(new ServletModelAttributeMethodProcessor(true));
return resolvers;
}
HandlerMethodArgumentResolver
参数解析器的实现
解析 key - value 形式的参数解析器 AbstractNamedValueMethodArgumentResolver
的子类
ExpressionValueMethodArgumentResolver
支持 @Value 注解参数和 SPEL 表达式
supportsParameter
方法:
java
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(Value.class);
}
MatrixVariableMapMethodArgumentResolver
支持 @MatrixVariable 注解 Map 类型参数
supportsParameter
方法:
java
@Override
public boolean supportsParameter(MethodParameter parameter) {
MatrixVariable matrixVariable = parameter.getParameterAnnotation(MatrixVariable.class);
return (matrixVariable != null && Map.class.isAssignableFrom(parameter.getParameterType()) &&
!StringUtils.hasText(matrixVariable.name()));
}
PathVariableMapMethodArgumentResolver
支持被 @PathVariable 注解的 Map 类型 @PathVariable value() 属性必须为空
supportsParameter
方法:
java
@Override
public boolean supportsParameter(MethodParameter parameter) {
PathVariable ann = parameter.getParameterAnnotation(PathVariable.class);
return (ann != null && Map.class.isAssignableFrom(parameter.getParameterType()) &&
!StringUtils.hasText(ann.value()));
}
PathVariableMethodArgumentResolver
支持被 @PathVariable 注解的参数
supportsParameter
方法:
java
@Override
public boolean supportsParameter(MethodParameter parameter) {
if (!parameter.hasParameterAnnotation(PathVariable.class)) {
return false;
}
if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
PathVariable pathVariable = parameter.getParameterAnnotation(PathVariable.class);
return (pathVariable != null && StringUtils.hasText(pathVariable.value()));
}
return true;
}
RequestAttributeMethodArgumentResolver
支持被 @RequestAttribute 注解的参数
supportsParameter
方法:
java
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(RequestAttribute.class);
}
RequestHeaderMethodArgumentResolver
支持被 @RequestHeader 注解的参数
supportsParameter
方法:
java
@Override
public boolean supportsParameter(MethodParameter parameter) {
return (parameter.hasParameterAnnotation(RequestHeader.class) &&
!Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType()));
}
···
#### `RequestParamMethodArgumentResolver` 支持被 @RequestParam 注解的参数
`supportsParameter` 方法:
```java
@Override
public boolean supportsParameter(MethodParameter parameter) {
if (parameter.hasParameterAnnotation(RequestParam.class)) {
if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
RequestParam requestParam = parameter.getParameterAnnotation(RequestParam.class);
return (requestParam != null && StringUtils.hasText(requestParam.name()));
}
else {
return true;
}
}
else {
if (parameter.hasParameterAnnotation(RequestPart.class)) {
return false;
}
parameter = parameter.nestedIfOptional();
if (MultipartResolutionDelegate.isMultipartArgument(parameter)) {
return true;
}
else if (this.useDefaultResolution) {
return BeanUtils.isSimpleProperty(parameter.getNestedParameterType());
}
else {
return false;
}
}
}
SessionAttributeMethodArgumentResolver
支持被 @SessionAttribute 注解的参数
supportsParameter
方法:
java
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(SessionAttribute.class);
}
ServletCookieValueMethodArgumentResolver
支持 @CookieValue 注解参数
supportsParameter
方法:
java
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(CookieValue.class);
}
支持字节流形式的参数解析器
AbstractMessageConverterMethodArgumentResolver
类和 AbstractMessageConverterMethodProcessor
的子类。其实现主要是依赖于 HttpMessageConverter
组件,之后的内容中会介绍到。
HttpEntityMethodProcessor
支持 HttpEntity 和 RequestEntity 类型参数
supportsParameter
方法:
java
@Override
public boolean supportsParameter(MethodParameter parameter) {
return (HttpEntity.class == parameter.getParameterType() ||
RequestEntity.class == parameter.getParameterType());
}
RequestResponseBodyMethodProcessor
支持 @RequestBody 注解的参数
supportsParameter
方法:
java
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(RequestBody.class);
}
RequestPartMethodArgumentResolver
支持 @RequestPart 注解的参数或 MultipartFile 类型参数或 MultipartFile 类型的集合参数
supportsParameter
方法:
java
@Override
public boolean supportsParameter(MethodParameter parameter) {
if (parameter.hasParameterAnnotation(RequestPart.class)) {
return true;
}
else {
if (parameter.hasParameterAnnotation(RequestParam.class)) {
return false;
}
return MultipartResolutionDelegate.isMultipartArgument(parameter.nestedIfOptional());
}
}
支持一些特定类型和 SpringMVC
传参规则的参数解析器
ErrorsMethodArgumentResolver
支持 org.springframework.validation.Errors 类型参数
supportsParameter
方法:
java
@Override
public boolean supportsParameter(MethodParameter parameter) {
Class<?> paramType = parameter.getParameterType();
return Errors.class.isAssignableFrom(paramType);
}
MapMethodProcessor
支持 Map 类型没有注解标注的
supportsParameter
方法:
java
@Override
public boolean supportsParameter(MethodParameter parameter) {
return (Map.class.isAssignableFrom(parameter.getParameterType()) &&
parameter.getParameterAnnotations().length == 0);
}
SessionStatusMethodArgumentResolver
支持 org.springframework.web.bind.support.SessionStatus 类型的参数
supportsParameter
方法:
java
@Override
public boolean supportsParameter(MethodParameter parameter) {
return SessionStatus.class == parameter.getParameterType();
}
RedirectAttributesMethodArgumentResolver
支持 org.springframework.web.servlet.mvc.support.RedirectAttributes 类型的参数
supportsParameter
方法:
java
@Override
public boolean supportsParameter(MethodParameter parameter) {
return RedirectAttributes.class.isAssignableFrom(parameter.getParameterType());
}
ModelAttributeMethodProcessor
支持被 @ModelAttribute 注解的参数
supportsParameter
方法:
java
@Override
public boolean supportsParameter(MethodParameter parameter) {
return (parameter.hasParameterAnnotation(ModelAttribute.class) ||
(this.annotationNotRequired && !BeanUtils.isSimpleProperty(parameter.getParameterType())));
}
ModelMethodProcessor
支持 org.springframework.ui.Model 类型的参数
supportsParameter
方法:
java
@Override
public boolean supportsParameter(MethodParameter parameter) {
return Model.class.isAssignableFrom(parameter.getParameterType());
}
UriComponentsBuilderMethodArgumentResolver
支持 UriComponentsBuilder 或ServletUriComponentsBuilder 类型的参数
supportsParameter
方法:
java
@Override
public boolean supportsParameter(MethodParameter parameter) {
Class<?> type = parameter.getParameterType();
return (UriComponentsBuilder.class == type || ServletUriComponentsBuilder.class == type);
}
ServletRequestMethodArgumentResolver
支持一些 Servlet API
参数类型
这个参数解析器支持的类型比较多主要是一些 Servlet API
和一些 JDK
中的数据类型和 SpringMVC
自定义的类型,WebRequest
, ServletRequest
, MultipartRequest
, HttpSession
, Principal
, InputStream
, Reader
, HttpMethod
, Locale
, TimeZone
, ZoneId
。
supportsParameter
方法:
java
@Override
public boolean supportsParameter(MethodParameter parameter) {
Class<?> paramType = parameter.getParameterType();
return (WebRequest.class.isAssignableFrom(paramType) ||
ServletRequest.class.isAssignableFrom(paramType) ||
MultipartRequest.class.isAssignableFrom(paramType) ||
HttpSession.class.isAssignableFrom(paramType) ||
(pushBuilder != null && pushBuilder.isAssignableFrom(paramType)) ||
(Principal.class.isAssignableFrom(paramType) && !parameter.hasParameterAnnotations()) ||
InputStream.class.isAssignableFrom(paramType) ||
Reader.class.isAssignableFrom(paramType) ||
HttpMethod.class == paramType ||
Locale.class == paramType ||
TimeZone.class == paramType ||
ZoneId.class == paramType);
}
有读者对内容中画的示意图感兴趣的话可以使用 www.drawio.com/ , 完全免费支持多种存储, 我是存储在 github
上。也可以使用 idea
内的 Diagrams.net Integration
插件,这样就可以在 idea
内作图了,完成后也可以直接提交到 github
。
DevXTalk
会持续分享有趣的技术和见闻,如果你觉得本文对你有帮助希望你可以分享给更多的朋友看到。该文章会同步在微信公众号 【DevXTalk】, 方便在微信客户端阅读。