背景:
本文作为SpringMVC系列的第四篇,介绍参数解析器。本文讨论的参数解析表示从HTTP消息中解析出JAVA对象或流对象并传参给Controller接口的过程。
本文内容包括介绍参数解析器工作原理、常见的参数解析器、自定义参数解析器等三部分。其中,原理部分会结合源码进行说明。
1.工作原理
说明:本文重点在于说明参数解析器的工作原理和使用方式,为避免文章过于冗长,会刻意省略对异步请求和文件上传部分的分支逻辑,读者可在理解主线逻辑后自定阅读该部分源码。
源码介绍时,会忽略所有的日志打印以及与主线逻辑无关的try-catch-finnally块
1.1 解析过程
SpringMVC系列-2 HTTP请求调用链 中介绍过:收到http请求后,进入DispatcherServlet的dispatcherServlet方法:
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {//...// 1.preHandleif (!mappedHandler.applyPreHandle(processedRequest, response)) {return;}// 2.call handlermv = ha.handle(processedRequest, response, mappedHandler.getHandler());// 3.postHandlemappedHandler.applyPostHandle(processedRequest, response, mv);//...
}
在执行HandlerInterceptor的preHandle和postHandle之间,会通过ha.handle(processedRequest, response, mappedHandler.getHandler())
反射调用Controller接口,跟踪该调用链进入:
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {// ⚠️1.调用controller接口Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);// ⚠️2.处理返回值setResponseStatus(webRequest);if (returnValue == null) {if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {disableContentCachingIfNecessary(webRequest);mavContainer.setRequestHandled(true);return;}} else if (StringUtils.hasText(getResponseStatusReason())) {mavContainer.setRequestHandled(true);return;}mavContainer.setRequestHandled(false);Assert.state(this.returnValueHandlers != null, "No return value handlers");try {this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest);} catch (Exception ex) {if (logger.isTraceEnabled()) {logger.trace(formatErrorForReturnValue(returnValue), ex);}throw ex;}
}
invokeAndHandle
方法从逻辑上可以分为两个部分:(1)调用controller接口并获取返回值;(2)处理返回值。
本文关注第一部分:Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
其中:webRequest包装了HTTP请求的request和response对象,mavContainer是MVC对象,providedArgs传入的是null.
跟进invokeForRequest方法:
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);return doInvoke(args);
}
包括两个步骤:(1)调用getMethodArgumentValues获取参数;(2)将步骤(1)获取的参数传递给doInvoke,通过反射调用Controller接口并返回结果。
跟进getMethodArgumentValues方法:
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {MethodParameter[] parameters = getMethodParameters();if (ObjectUtils.isEmpty(parameters)) {return new Object[0];}Object[] args = new Object[parameters.length];for (int i = 0; i < parameters.length; i++) {MethodParameter parameter = parameters[i];parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);args[i] = findProvidedArgument(parameter, providedArgs);if (args[i] != null) {continue;}if (!this.resolvers.supportsParameter(parameter)) {throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));}try {args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);} catch (Exception ex) {throw ex;}}return args;
}
由于入参providedArgs为null, 因此上述逻辑可以简化为:
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {MethodParameter[] parameters = getMethodParameters();if (ObjectUtils.isEmpty(parameters)) {return new Object[0];}Object[] args = new Object[parameters.length];for (int i = 0; i < parameters.length; i++) {MethodParameter parameter = parameters[i];parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);if (!this.resolvers.supportsParameter(parameter)) {throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));}try {args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);} catch (Exception ex) {throw ex;}}return args;
}
上述解析参数的逻辑可以分为两步:(1)获取目标接口的参数数组,并判断是否为空(参数为空即不需要处理参数);(2)遍历参数数组,根据参数解析器对每个参数对象依此进行处理,如果没有匹配的参数处理器,则抛出IllegalStateException异常。
这里的参数解析器this.resolvers
类型是HandlerMethodArgumentResolverComposite
,是一个组合模型,内部维持了一个HandlerMethodArgumentResolver数组:
private final List<HandlerMethodArgumentResolver> argumentResolvers
参数解析最终都会派发给argumentResolvers的各个元素,派发原则是选择第一个满足匹配规则的参数解析器
。
在后续SpringMVC源码介绍过程中,会发现框架大量使用了组合设计模式;大部分采取匹配+处理的组合手段完成。
因此HandlerMethodArgumentResolver接口需要有两个接口:
public interface HandlerMethodArgumentResolver {boolean supportsParameter(MethodParameter parameter);Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;
}
supportsParameter方法用于判断该HandlerMethodArgumentResolver是否与参数匹配,resolveArgument方法用于解析参数并返回解析结果。
1.2 初始化过程
ServletInvocableHandlerMethods的resolvers属性
解析过程的核心在于参数解析器,即ServletInvocableHandlerMethod对象中的HandlerMethodArgumentResolverComposite resolvers
属性,而每次HTTP调用都会生成一个ServletInvocableHandlerMethod对象,因此关注该对象的resolvers属性如何被初始化即可:
public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter implements BeanFactoryAware, InitializingBean {protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {// ...ServletInvocableHandlerMethod invocableMethod = new ServletInvocableHandlerMethod(handlerMethod);if (this.argumentResolvers != null) {invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);}// ...invocableMethod.invokeAndHandle(webRequest, mavContainer);// ...}
}
如上所示:ServletInvocableHandlerMethod对象的resolvers属性来自RequestMappingHandlerAdapter对象的this.argumentResolvers
属性。
RequestMappingHandlerAdapter的argumentResolvers属性
继续跟踪RequestMappingHandlerAdapter的this.argumentResolvers
属性初始化过程,需要注意的是RequestMappingHandlerAdapter是全局Bean对象,因此可以从头梳理一下该对象关于argumentResolvers属性的初始化过程。
【1】实例化阶段
@Bean
public RequestMappingHandlerAdapter requestMappingHandlerAdapter(@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,@Qualifier("mvcConversionService") FormattingConversionService conversionService,@Qualifier("mvcValidator") Validator validator) {RequestMappingHandlerAdapter adapter = createRequestMappingHandlerAdapter();adapter.setCustomArgumentResolvers(getArgumentResolvers());// ...return adapter;
}
createRequestMappingHandlerAdapter()返回RequestMappingHandlerAdapter实例后,将getArgumentResolvers()获取的自定义参数解析器设置到this.customArgumentResolvers
属性中。
看一下getArgumentResolvers()
逻辑:
protected final List<HandlerMethodArgumentResolver> getArgumentResolvers() {this.argumentResolvers = new ArrayList<>();addArgumentResolvers(this.argumentResolvers);return this.argumentResolvers;
}@Override
protected void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {this.configurers.addArgumentResolvers(argumentResolvers);
}
argumentResolvers数据来自于this.configurers,看一下这个属性的初始化过程:
@Configuration(proxyBeanMethods = false)
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();@Autowired(required = false)public void setConfigurers(List<WebMvcConfigurer> configurers) {if (!CollectionUtils.isEmpty(configurers)) {this.configurers.addWebMvcConfigurers(configurers);}}
}
configurers来自于IOC容器中WebMvcConfigurer类型的对象。
因此用户可自定义WebMvcConfigurer对象并将其注入到IOC中,在自定义的WebMvcConfigurer类中通过复写addArgumentResolvers方法可实现自定义参数解析器的添加。
【2】初始化阶段
RequestMappingHandlerAdapter实现了InitializingBean接口,在Bean的初始化阶段中,会调用其afterPropertiesSet()钩子函数:
void afterPropertiesSet() {// ...if (this.argumentResolvers == null) {List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);}// ...
}
public
可以看出所有的参数解析器来自有getDefaultArgumentResolvers()
方法:
private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>(30);// Annotation-based argument resolutionresolvers.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 resolutionresolvers.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 argumentsif (getCustomArgumentResolvers() != null) {resolvers.addAll(getCustomArgumentResolvers());}// Catch-allresolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));resolvers.add(new ServletModelAttributeMethodProcessor(true));return resolvers;
}
这里包含了框架内置的参数解析器以及自定义参数解析器,需要注意一下几点:
(1)getCustomArgumentResolvers()来自于【1】实例化阶段中设置的自定义参数解析器;
(2)自定义参数解析器的顺序比较靠后,需要避免被其他参数解析器拦截,supportsParameter方法可以根据参数类型进行匹配。
(3)首位端各存在一个RequestParamMethodArgumentResolver类型的参数解析器,区别是内部useDefaultResolution属性前者是false, 后者是true.
2.常见的参数解析器
2.1 解析器分类
Debug是阅读源码的一个很有效的方式。
Debug:
Spring框架默认的消息解析器超过20个, 红框圈选的是常见的参数解析器(本章节重点对着一部分进行介绍),如下所示:
上述26个参数解析器按照匹配条件类型可以分为:
【1】注解类型
根据Controller接口中参数是否拥有指定注解确定使用的参数解析器:
@RequestParam
-> RequestParamMethodArgumentResolver, RequestParamMapMethodArgumentResolver
@PathVariable
-> PathVariableMethodArgumentResolver, PathVariableMapMethodArgumentResolver
@MatrixVariable
-> MatrixVariableMethodArgumentResolver, MatrixVariableMapMethodArgumentResolver
@ModelAttribute
-> ModelAttributeMethodProcessor
@RequestBody
-> RequestResponseBodyMethodProcessor
@RequestPart
-> RequestPartMethodArgumentResolver
@RequestHeader
-> RequestHeaderMethodArgumentResolver, RequestHeaderMapMethodArgumentResolver
@CookieValue
-> ServletCookieValueMethodArgumentResolver
@Value
-> ExpressionValueMethodArgumentResolver
@SessionAttribute
-> SessionAttributeMethodArgumentResolver
@RequestAttribute
-> RequestAttributeMethodArgumentResolver
上述参数解析器根据是否有对应注解(以及满足特定条件)确定是否匹配参数。
【2】参数类型
根据Controller接口中参数类型确定使用的参数解析器。
ServletResponse
及其子类, OutputStream
及其子类, Writer
及其子类 -> ServletResponseMethodArgumentResolver
HttpEntity
, RequestEntity
-> HttpEntityMethodProcessor
RedirectAttributes
及其子类 -> RedirectAttributesMethodArgumentResolver
Model
及其子类 -> ModelMethodProcessor
Errors
及其子类 -> ErrorsMethodArgumentResolver
SessionStatus
-> SessionStatusMethodArgumentResolver
UriComponentsBuilder
, ServletUriComponentsBuilder
-> UriComponentsBuilderMethodArgumentResolver
部分参数解析器的supportsParameter方法条件比较复杂,如下所示:
ServletRequestMethodArgumentResolver:
支持的类型较多,如下所示:
@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) ||InputStream.class.isAssignableFrom(paramType) ||Reader.class.isAssignableFrom(paramType) ||HttpMethod.class == paramType ||Locale.class == paramType ||TimeZone.class == paramType ||ZoneId.class == paramType);
}
MapMethodProcessor:
public boolean supportsParameter(MethodParameter parameter) {return Map.class.isAssignableFrom(parameter.getParameterType()) &¶meter.getParameterAnnotations().length == 0;
}
参数不能有注解,且参数为Map类型.
ServletModelAttributeMethodProcessor:
@Override
public boolean supportsParameter(MethodParameter parameter) {return (parameter.hasParameterAnnotation(ModelAttribute.class) ||(this.annotationNotRequired && !BeanUtils.isSimpleProperty(parameter.getParameterType())));}
该解析器在实例化时设置的annotationNotRequired为true,因此可以简化为:
@Override
public boolean supportsParameter(MethodParameter parameter) {return parameter.hasParameterAnnotation(ModelAttribute.class) ||!BeanUtils.isSimpleProperty(parameter.getParameterType());}
表示:参数有@ModelAttribute注解或者BeanUtils.isSimpleProperty(parameter.getParameterType())返回false;
public static boolean isSimpleProperty(Class<?> type) {// 如果类型是数据调用isSimpleValueType判读数组的元素return isSimpleValueType(type) || type.isArray() && isSimpleValueType(type.getComponentType());
}public static boolean isSimpleValueType(Class<?> type) {return Void.class != type && Void.TYPE != type && (ClassUtils.isPrimitiveOrWrapper(type) || Enum.class.isAssignableFrom(type) || CharSequence.class.isAssignableFrom(type) || Number.class.isAssignableFrom(type) || Date.class.isAssignableFrom(type) || Temporal.class.isAssignableFrom(type) || URI.class == type || URL.class == type || Locale.class == type || Class.class == type);
}
即:参数类型(或参数数组的元素类型)是Void或Void.TYPE或者均不是(Enum,Number,CharSequence,Locale,…)类型的才会匹配。
2.2 常用注解及消息解析器
略(待补充)
3.使用方式
案例从请求url中解析出name和age、从HTTP请求头中解析出token、并获取当前服务器时间,用于构造User对象,并传参给Controller接口。
定义参数类:
@Data
public class User {private String name;private Integer age;private String token;private LocalDateTime time;
}
定义Controller接口:
@GetMapping("/test")
public Object queryByType(User user) {return user;
}
自定义参数解析器:
public class MyArgumentResolver implements HandlerMethodArgumentResolver {@Overridepublic boolean supportsParameter(MethodParameter parameter) {return parameter.getParameterType().equals(User.class);}@Overridepublic Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {User user = new User();HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest();user.setName(request.getParameter("name"));user.setAge(Integer.parseInt(request.getParameter("age")));user.setToken(request.getHeader("token"));user.setTime(LocalDateTime.now());return user;}
}
将参数解析器注册到容器中:
@Configuration
public class BaseWebMvcConfigurer implements WebMvcConfigurer {@Overridepublic void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {argumentResolvers.add(new MyArgumentResolver());}
}
使用postman调用测试结果如下: