HandlerMethodArgumentResolver
HandlerMethodArgumentResolver
是用来为处理器解析参数的主要用在前面讲过的 lnvocableHandlerMethod
中。 每个Resolver 对应一种类型的参数, 所以我们可以从下图的继承结构图看出它的实现有很多:
其中HandlerMethodArgumentResolverComposite
并不是一个解析器,是一个解析器的合集,可以将其他解析器放置在里面,通过暴露的方法来判断具体调用哪一个解析器,然后执行参数解析。这个类的方法很少也很简单,比较关键的两个方法
resolveArgument
:解析参数
supportsParameter
:遍历resolvers
,判断是否可以解析传入的参数
HandlerMethodArgumentResolver
实现类一般有两种命名方式, 一种是 XXXMethodArgumentResolver
, 另一种是 XXXMethodProcessor
。前者表示一个参数解析器, 后者除了可以解析参数外还可以处理相应类型的返回值, 也就是同时还是后面要讲到的 HandlerMethodReturnValueHandler
。 其中的 XXX 表示用于解析的参数的类型。 另外, 还有个 Adapter, 它也不是直接解析参数的, 而是用来兼容 WebArgumentResolver
类型的参数解析器的适配器。
解析器
AbstractMessageConverterMethodArgumentResolver : 使用 HttpMessageConverter
解析requestbody
类型参数的基类。
--AbstractMessageConverterMethodProcessor : 定义相关工具, 添加返回值处理。
----HttpEntityMethodProcessor: 解析HttpEntity
和 RequestEntity
类型的参数。
----RequestResponseBodyMethodProcessor : 使用HttpMessageConverter
解析@RequestBody
参数;处理使用@ResponseBody
的返回值
--RequestPartMethodArgumentResolver : 解析使用@RequestPart
注解的参数;MultipartFile
和Part
的子类参数比如javax.servlet.http.Part
类型的参数。
AbstractNamedValueMethodArgumentResolver : 解析 namedValue
类型的参数(有name的参数, 如 cookie 、requestParam 、requestHeader 、pathVariable
等) 的基类, 主要功能有: 1.获取name ; 2.resolveDefaultValue、handleMissingValue、handleNullValue
;3.调用模板方法 resolveName
、handleResolvedValue
具体解析。
--AbstractCookieValueMethodArgumentResolver :解析使用@CookieValue
注解的参数
----ServletCookieValueMethodArgumentResolver : 实现 resolveName
方法,从HttpServletRequest
中解析出cookie值
---ExpressionValueMethodArgumentResolver : 解析@Value
注解的参数,主要设置了 beanFactory
, 并用它完成具体解析, 解析过程在父类完成。
--MatrixVariableMethodArgumentResolver :解析@MatrixVariable
注解的参数,不包括map类型
--PathVariableMethodArgumentResolver :解析使用@PathVariable
注解的参数,从uri中解析参数
--RequestAttributeMethodArgumentResolver :解析使用@RequestAttribute
的参数
--RequestHeaderMethodArgumentResolver :解析使用@RequestHeader
注解的参数,不包括map类型
--RequestParamMethodArgumentResolver :解析从request流中获取值的参数,如@RequestParam
,MultipartFile
,Part
,和未使用@RequestParam
注解的简单类型(int
,long
等)也被视为具有从参数名称派生的参数名称的请求参数
--SessionAttributeMethodArgumentResolver :解析使用@SessionAttribute
注解的参数
AbstractWebArgumentResolverAdapter 使用@WebArgumentResolver
的基类,用于向后兼容.适配WebArgumentResolver
--ServletWebArgumentResolverAdapter :新建NativeWebRequest
,提供给父类
ErrorsMethodArgumentResolver :处理Errors
子类参数
MapMethodProcessor :解析Map型参数(包括ModelMap类 型),直接返回mavContainer
中的model
。
MatrixVariableMapMethodArgumentResolver :使用@MatrixVariable
注解的map类型参数
ModelAttributeMethodProcessor :解决@ModelAttribute
注解的方法参数,并处理@ModelAttribute
注解的方法的返回值
--ServletModelAttributeMethodProcessor :使用ServletRequestDataBinder
替代父类的WebDataBinder
进行参数绑定
ModelMethodProcessor :解析model
类型参数,直接返回mavContainer
中的model
。
PathVariableMapMethodArgumentResolver :解析使用@PathVariable
注解的map参数
RedirectAttributesMethodArgumentResolver :解析RedirectAttributes
类型参数,新建RedirectAttributesModelMap
类型的RedirectAttributes
并设置到mavContainer
中, 然后返回给我们的参数
RequestHeaderMapMethodArgumentResolver :解析使用@RequestHeader
注解的map参数
RequestParamMapMethodArgumentResolver :解析使用@RequestParam
注解的map参数,而且注解中有value的参数
ServletRequestMethodArgumentResolver :解析request相关的参数,例如WebRequest、ServletRequest、MultipartRequest、 HttpSession、Principal、Locale、Timezone、lnputStream、Reader、HttpMethod
类型和Zoneld
类型的参数.
ServletResponseMethodArgumentResolver:解析ServletResponse
、OutputStreamWriter
、Writer
类型的参数
SessionStatusMethodArgumentResolver :解析SessionStatus
类型参数,直接返回mavContainer
中的SessionStatus
。
UriComponentsBuilderMethodArgumentResolver :解析UriComponentsBuilder
类型参数
当然这么多解析不能一个个去分析,我们只挑拣我们平常开发中最常用的几个来分析
RequestParamMethodArgumentResolver
在日常开发中我们会经常用到@RequestParam
注解来完成get请求,现在我们来看下get请求中使用@RequestParam是如何被绑定的。
我们先把目光放回到ServletInvocableHandlerMethod#invokeAndHandle
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
。。。。。省略
}
再到invokeForRequest
@Nullable
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
。。。。省略
}
再到getMethodArgumentValues
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
MethodParameter[] parameters = getMethodParameters();
//省略。。
if (!this.resolvers.supportsParameter(parameter)) {
throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
}
args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
//省略。。
}
我们就从这开始分析这个resolvers
,首先在前面分析过了在初始化的时候会把一系列的默认resolvers
放置进来,HandlerMethodArgumentResolverComposite
这个类里存储的就是大量的默认以及自定义的解析器集合。
他的父类接口HandlerMethodArgumentResolver
仅有两个方法
//判断是否当前参数的解析
boolean supportsParameter(MethodParameter parameter);
//解析参数
Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;
在HandlerMethodArgumentResolverComposite
里实现是通过遍历所有的解析器看内置的处理器能否处理这个参数,如果全部不能则报错。
@Override
public boolean supportsParameter(MethodParameter parameter) {
return getArgumentResolver(parameter) != null;
}
@Nullable
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
if (result == null) {
for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
if (resolver.supportsParameter(parameter)) {
result = resolver;
this.argumentResolverCache.put(parameter, result);
break;
}
}
}
return result;
}
判断之后就说明这个参数是可以被处理的,进入解析:
@Override
@Nullable
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
if (resolver == null) {
throw new IllegalArgumentException("Unsupported parameter type [" +
parameter.getParameterType().getName() + "]. supportsParameter should be called first.");
}
return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
}
同样这里又做了一次判断,我觉得判断有点多余了,但是异常信息是不一样的。遍历找到一个可以解析的处理器之后到对应的解析器的方法中进行处理,这里我们使用RequestParamMethodArgumentResolver
解析器处理我们的参数,我们进到这个类里看看:
可以看到又是熟悉的接口-抽象类-实现的层级结构,我们先不关注UriComponentsContributor
。AbstractNamedValueMethodArgumentResolver
为子类定义如何执行以下操作:
- 获取方法参数的命名值信息
- 将名称解析为参数值
- 需要参数值时处理缺失的参数值
- (可选)处理解析值
默认值字符串可以包含$ {...}占位符和Spring表达式语言#{...}表达式。 为此,必须将ConfigurableBeanFactory
提供给类构造函数。如果WebDataBinder
与方法参数类型不匹配,则会创建一个WebDataBinder
以将类型转换应用于解析的参数值。
@Override
@Nullable
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
//处理@RequestParam注解
NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
MethodParameter nestedParameter = parameter.nestedIfOptional();
//处理配置文件的配置值,如果name的值是通过配置文件配置可变
Object resolvedName = resolveStringValue(namedValueInfo.name);
if (resolvedName == null) {
throw new IllegalArgumentException(
"Specified name must not resolve to null: [" + namedValueInfo.name + "]");
}
//从请求中捞到入参值
Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
if (arg == null) {
if (namedValueInfo.defaultValue != null) {
arg = resolveStringValue(namedValueInfo.defaultValue);
}
else if (namedValueInfo.required && !nestedParameter.isOptional()) {
handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
}
arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
}
else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
arg = resolveStringValue(namedValueInfo.defaultValue);
}
if (binderFactory != null) {
WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
try {
arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
}
catch (ConversionNotSupportedException ex) {
throw new MethodArgumentConversionNotSupportedException(arg, ex.getRequiredType(),
namedValueInfo.name, parameter, ex.getCause());
}
catch (TypeMismatchException ex) {
throw new MethodArgumentTypeMismatchException(arg, ex.getRequiredType(),
namedValueInfo.name, parameter, ex.getCause());
}
}
handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);
return arg;
}
先看getNamedValueInfo
,其实是为了拿到注解@RequestParam
的信息,如name,required,defaultValue
,而updateNamedValueInfo
是针对如果注解里没有标明name就拿方法参数名作为name。
private NamedValueInfo getNamedValueInfo(MethodParameter parameter) {
NamedValueInfo namedValueInfo = this.namedValueInfoCache.get(parameter);
if (namedValueInfo == null) {
namedValueInfo = createNamedValueInfo(parameter);
namedValueInfo = updateNamedValueInfo(parameter, namedValueInfo);
this.namedValueInfoCache.put(parameter, namedValueInfo);
}
return namedValueInfo;
}
@Override
protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
RequestParam ann = parameter.getParameterAnnotation(RequestParam.class);
return (ann != null ? new RequestParamNamedValueInfo(ann) : new RequestParamNamedValueInfo());
}
private NamedValueInfo updateNamedValueInfo(MethodParameter parameter, NamedValueInfo info) {
String name = info.name;
if (info.name.isEmpty()) {
name = parameter.getParameterName();
if (name == null) {
throw new IllegalArgumentException(
"Name for argument type [" + parameter.getNestedParameterType().getName() +
"] not available, and parameter name information not found in class file either.");
}
}
String defaultValue = (ValueConstants.DEFAULT_NONE.equals(info.defaultValue) ? null : info.defaultValue);
return new NamedValueInfo(name, info.required, defaultValue);
}
处理完注解的事宜,我们现在得到了一个NamedValueInfo
,表示我们的参数名,以及默认值还有必填性,我们现在需要拿这个参数名去请求里找到传入的数据,然后绑定到参数上,在某些情况下我们使用@RequestParam
注解的name
配置了可变,并将值配置在配置文件里,那么就需要先把这个值替换成真正的name
再去寻找参数,如果没有就是原本的name
,我们接着往下看重头戏:
Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
这是一个抽象方法,子类RequestParamMethodArgumentResolver
实现代码:
@Override
@Nullable
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);
if (servletRequest != null) {
//判断是否是MultipartArgument
Object mpArg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest);
if (mpArg != MultipartResolutionDelegate.UNRESOLVABLE) {
return mpArg;
}
}
//尝试提取请求中的multipart文件
Object arg = null;
MultipartRequest multipartRequest = request.getNativeRequest(MultipartRequest.class);
if (multipartRequest != null) {
List<MultipartFile> files = multipartRequest.getFiles(name);
if (!files.isEmpty()) {
arg = (files.size() == 1 ? files.get(0) : files);
}
}
//如果没有再从请求中根据name获取参数值
if (arg == null) {
String[] paramValues = request.getParameterValues(name);
if (paramValues != null) {
arg = (paramValues.length == 1 ? paramValues[0] : paramValues);
}
}
return arg;
}
忽略掉上传文件的部分,看起来获取值很简单,我们通过Tomcat的门面类轻松地拿到了参数值,但是到这里远没有结束,接收是通过数组接收,这时参数的类型其实还是不确定的,也就是还没真正做到参数的绑定!
所以接下去开始要进行真正的类型绑定了,跳过参数为null为空的那段判断,先判断binderFactory是否为null,这个参数在RequestMappingHandlerAdapter#invokeHandlerMethod
其实就已经被设置了:
invocableMethod.setDataBinderFactory(binderFactory);
所以这里的binderFactory
的类型是ServletRequestDataBinderFactory
,类结构图如下
@Override
@SuppressWarnings("deprecation")
public final WebDataBinder createBinder(
NativeWebRequest webRequest, @Nullable Object target, String objectName) throws Exception {
WebDataBinder dataBinder = createBinderInstance(target, objectName, webRequest);
//初始化工作
if (this.initializer != null) {
this.initializer.initBinder(dataBinder, webRequest);
}
//处理@InitBinder注解 模板方法
initBinder(dataBinder, webRequest);
return dataBinder;
}
子类InitBinderDataBinderFactory
实现了这个方法,还记得我们在前面捞取了@InitBinder
注解的方法并存到binderMethods
吗,这里就在执行这些方法,这里将我们创建的dataBinder
实例传递进去进行注册我们自定义的CustomEditor
。我们平时可能会需要自定义一些参数绑定例如:
@InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(List.class, "extendStr", new CustomJsonEditor(List.class));
}
这里只是举了个简单的使用示例,注册了CustomEditor
,当在参数绑定的时候会将符合的参数进行绑定,当然了一个controller
里可能存在参数名称重复的情况,所以这时候@InitBinder
注解的value
就可以发挥作用,你可以指定绑定到哪一个对象。例如@InitBinder("studentReq")
诸如此类的用法,下面是这个初始化绑定对象的方法的源码,invokeForRequest
方法在前面分析HandlerAdapt
的时候提到过这里不再做分析。
@Override
public void initBinder(WebDataBinder dataBinder, NativeWebRequest request) throws Exception {
for (InvocableHandlerMethod binderMethod : this.binderMethods) {
if (isBinderMethodApplicable(binderMethod, dataBinder)) {
Object returnValue = binderMethod.invokeForRequest(request, null, dataBinder);
if (returnValue != null) {
throw new IllegalStateException(
"@InitBinder methods must not return a value (should be void): " + binderMethod);
}
}
}
}
完成dataBinder
的初始化之后就要准备真正的数据类型绑定
binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
@Override
@Nullable
public <T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType,
@Nullable MethodParameter methodParam) throws TypeMismatchException {
return getTypeConverter().convertIfNecessary(value, requiredType, methodParam);
}
找到对应的类型的转换器,这里的转换器是SimpleTypeConverter
,一直往下走,到TypeConverterDelegate#convertIfNecessary
@Nullable Class<T> requiredType, @Nullable TypeDescriptor typeDescriptor) throws IllegalArgumentException {
// Custom editor for this type?
PropertyEditor editor = this.propertyEditorRegistry.findCustomEditor(requiredType, propertyName);
ConversionFailedException conversionAttemptEx = null;
// No custom editor but custom ConversionService specified?
ConversionService conversionService = this.propertyEditorRegistry.getConversionService();
if (editor == null && conversionService != null && newValue != null && typeDescriptor != null) {
TypeDescriptor sourceTypeDesc = TypeDescriptor.forObject(newValue);
if (conversionService.canConvert(sourceTypeDesc, typeDescriptor)) {
try {
//类型转换
return (T) conversionService.convert(newValue, sourceTypeDesc, typeDescriptor);
}
catch (ConversionFailedException ex) {
// fallback to default conversion logic below
conversionAttemptEx = ex;
}
}
}
。。。。。省略
}
conversionService
内置了大量的预定义的转换,String转int在预定义的转换中,所以直接进行了转换。完成参数绑定后我们回到AbstractNamedValueMethodArgumentResolver#resolveArgument
方法中发现了方法之后还执行handleResolvedValue
,这是一个空方法,交由子类覆写,但是当前的处理器并没有覆写这个方法,至此参数的绑定成功完成。
ServletModelAttributeMethodProcessor
我们这里用一个刻意的例子描述我们另一个常用的对象绑定方式:
@GetMapping(value = "/delete")
public void delete(Student student) {
//do nothing
}
请求URL:http://localhost:8081/student/get-student?id=1&name=小米
,这样子Spring也是刻意很聪明地帮助我们绑定到对象里,接下去就看看这个方式的参数解析以及绑定吧!
首先需要知道是哪一个解析器在处理,很自然断点到HandlerMethodArgumentResolverComposite#resolveArgument
查看具体过滤出来的处理器,原来是ServletModelAttributeMethodProcessor
:
在前面提到过Processor是用来处理返回值的,这里以Processor
命名说明既可以处理入参也可以处理结果,它的父类ModelAttributeMethodProcessor
是用来支持@ModelAttribute
注解的方法参数,这里代码进行复用用来绑定参数到对象上,直接看它的resolveArgument
方法:
@Override
@Nullable
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
//参数名称,即要将参数绑定到的bean名称
String name = ModelFactory.getNameForParameter(parameter);
//这里暂不关心这个注解
ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class);
if (ann != null) {
mavContainer.setBinding(name, ann.binding());
}
Object attribute = null;
BindingResult bindingResult = null;
if (mavContainer.containsAttribute(name)) {
attribute = mavContainer.getModel().get(name);
}
else {
// Create attribute instance
//子类覆写这个方法来创建bean
try {
attribute = createAttribute(name, parameter, binderFactory, webRequest);
}
catch (BindException ex) {
if (isBindExceptionRequired(parameter)) {
// No BindingResult parameter -> fail with BindException
throw ex;
}
// Otherwise, expose null/empty value and associated BindingResult
if (parameter.getParameterType() == Optional.class) {
attribute = Optional.empty();
}
bindingResult = ex.getBindingResult();
}
}
if (bindingResult == null) {
// Bean property binding and validation;
// skipped in case of binding failure on construction.
//创建并初始化DataBinder,同上一个解析器的分析
WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
if (binder.getTarget() != null) {
if (!mavContainer.isBindingDisabled(name)) {
//参数绑定到对象上
bindRequestParameters(binder, webRequest);
}
//参数校验
validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
throw new BindException(binder.getBindingResult());
}
}
// Value type adaptation, also covering java.util.Optional
if (!parameter.getParameterType().isInstance(attribute)) {
attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);
}
bindingResult = binder.getBindingResult();
}
// Add resolved attribute and BindingResult at the end of the model
Map<String, Object> bindingResultModel = bindingResult.getModel();
mavContainer.removeAttributes(bindingResultModel);
mavContainer.addAllAttributes(bindingResultModel);
return attribute;
}
@Override
protected final Object createAttribute(String attributeName, MethodParameter parameter,
WebDataBinderFactory binderFactory, NativeWebRequest request) throws Exception {
//试图从请求里找到这个名称的值
String value = getRequestValueForAttribute(attributeName, request);
if (value != null) {
Object attribute = createAttributeFromRequestValue(
value, attributeName, parameter, binderFactory, request);
if (attribute != null) {
return attribute;
}
}
//找不到就创建这个名称的bean
return super.createAttribute(attributeName, parameter, binderFactory, request);
}
@Override
protected void bindRequestParameters(WebDataBinder binder, NativeWebRequest request) {
ServletRequest servletRequest = request.getNativeRequest(ServletRequest.class);
Assert.state(servletRequest != null, "No ServletRequest");
ServletRequestDataBinder servletBinder = (ServletRequestDataBinder) binder;
//绑定的方法
servletBinder.bind(servletRequest);
}
在上面的分析中,我们知道了这个解析器的处理流程:先试图从请求中找到有没有一样的参数名称,找不到就通过Spring的工具方法创建一个bean,但是值都是null,这时候就需要将请求中的数据绑定到bean上,所以就在ServletModelAttributeMethodProcessor#bindRequestParameters
深入下去看,我们不具体贴代码,调用层级十分多,只需要了解它的整个原理,bean被包装进BeanWrapper
,通过BeanWrapperImpl
内实现Java内省机制来完成赋值操作,可以参见BeanWrapperImpl.BeanPropertyHandler#setValue
@Override
public void setValue(final @Nullable Object value) throws Exception {
//拿到set方法执行
final Method writeMethod = (this.pd instanceof GenericTypeAwarePropertyDescriptor ?
((GenericTypeAwarePropertyDescriptor) this.pd).getWriteMethodForActualAccess() :
this.pd.getWriteMethod());
if (System.getSecurityManager() != null) {
AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
ReflectionUtils.makeAccessible(writeMethod);
return null;
});
try {
AccessController.doPrivileged((PrivilegedExceptionAction<Object>) () ->
writeMethod.invoke(getWrappedInstance(), value), acc);
}
catch (PrivilegedActionException ex) {
throw ex.getException();
}
}
else {
ReflectionUtils.makeAccessible(writeMethod);
//执行
writeMethod.invoke(getWrappedInstance(), value);
}
}
}
完后set方法的执行,就完成了入参的真正绑定到对象的过程,也许参数上面有需要校验的注解,那么再执行validateIfApplicable
方法进行校验,这里不做展开,至此完成了参数映射成对象的绑定。
RequestResponseBodyMethodProcessor
在前后端分离的框架中还有最常见的一种Post类型的请求,将参数以JSON串放置在body中传递到后端,使用@RequestBody
注解接收的方法。
同样这也是一个返回结果与入参解析器共用的处理器。
这个处理器层级就多了一点,首先了解一下它的两个抽象父类的作用
AbstractMessageConverterMethodArgumentResolver
:通过使用HttpMessageConverters
从请求的BODY中读取来解析方法参数值的基类。
AbstractMessageConverterMethodProcessor
:通过使用HttpMessageConverters
写入response来扩展AbstractMessageConverterMethodArgumentResolver
的能力,以处理方法返回值。
这下明白了,一个为入参服务一个为返回值服务
两个抽象类都没实现抽象方法resolveArgument
,而留给了RequestResponseBodyMethodProcessor
实现,所以直接定位到这个类里的这个方法:
@Override
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
parameter = parameter.nestedIfOptional();
// 通过HttpMessageConverter读取HTTP报文
Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
String name = Conventions.getVariableNameForParameter(parameter);
if (binderFactory != null) {
WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
if (arg != null) {
// 这里完成数据绑定+数据校验(绑定的错误和校验的错误都会放进Errors里)
validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
}
}
// 把错误消息放进去
// 后续逻辑会判断MODEL_KEY_PREFIX这个key
if (mavContainer != null) {
mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
}
}
return adaptArgumentIfNecessary(arg, parameter);
}
先看关键的readWithMessageConverters
方法,通过HttpMessageConverter
读取参数
@Override
protected <T> Object readWithMessageConverters(NativeWebRequest webRequest, MethodParameter parameter,
Type paramType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {
HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
Assert.state(servletRequest != null, "No HttpServletRequest");
ServletServerHttpRequest inputMessage = new ServletServerHttpRequest(servletRequest);
//继续往下看readWithMessageConverters方法
Object arg = readWithMessageConverters(inputMessage, parameter, paramType);
if (arg == null && checkRequired(parameter)) {
throw new HttpMessageNotReadableException("Required request body is missing: " +
parameter.getExecutable().toGenericString(), inputMessage);
}
return arg;
}
到了父类AbstractMessageConverterMethodArgumentResolver
protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter,
Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {
//处理contentType
MediaType contentType;
boolean noContentType = false;
try {
contentType = inputMessage.getHeaders().getContentType();
}
catch (InvalidMediaTypeException ex) {
throw new HttpMediaTypeNotSupportedException(ex.getMessage());
}
if (contentType == null) {
noContentType = true;
contentType = MediaType.APPLICATION_OCTET_STREAM;
}
Class<?> contextClass = parameter.getContainingClass();
Class<T> targetClass = (targetType instanceof Class ? (Class<T>) targetType : null);
if (targetClass == null) {
ResolvableType resolvableType = ResolvableType.forMethodParameter(parameter);
targetClass = (Class<T>) resolvableType.resolve();
}
HttpMethod httpMethod = (inputMessage instanceof HttpRequest ? ((HttpRequest) inputMessage).getMethod() : null);
Object body = NO_VALUE;
EmptyBodyCheckingHttpInputMessage message;
try {
message = new EmptyBodyCheckingHttpInputMessage(inputMessage);
for (HttpMessageConverter<?> converter : this.messageConverters) {
Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass();
GenericHttpMessageConverter<?> genericConverter =
(converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null);
if (genericConverter != null ? genericConverter.canRead(targetType, contextClass, contentType) :
(targetClass != null && converter.canRead(targetClass, contentType))) {
if (message.hasBody()) {
HttpInputMessage msgToUse =
getAdvice().beforeBodyRead(message, parameter, targetType, converterType);
body = (genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse) :
((HttpMessageConverter<T>) converter).read(targetClass, msgToUse));
body = getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType);
}
else {
body = getAdvice().handleEmptyBody(null, message, parameter, targetType, converterType);
}
break;
}
}
}
catch (IOException ex) {
throw new HttpMessageNotReadableException("I/O error while reading input message", ex, inputMessage);
}
if (body == NO_VALUE) {
if (httpMethod == null || !SUPPORTED_METHODS.contains(httpMethod) ||
(noContentType && !message.hasBody())) {
return null;
}
throw new HttpMediaTypeNotSupportedException(contentType, this.allSupportedMediaTypes);
}
MediaType selectedContentType = contentType;
Object theBody = body;
LogFormatUtils.traceDebug(logger, traceOn -> {
String formatted = LogFormatUtils.formatValue(theBody, !traceOn);
return "Read \"" + selectedContentType + "\" to [" + formatted + "]";
});
return body;
}
首先需要处理contentType
,因为contentType
关系到要用哪个converter来处理body的数据,如果你有自己制定contentType,那么以你为准否则使用默认的MediaType.APPLICATION_OCTET_STREAM
,接下去拿到HttpMethod
,在body没拿到数据的时候进行判断,到底是没数据传进来还是是一个异常的请求,这是后面的代码的事,在拿到HttpMethod之后就要开始关键的处理,假定我们的contentType是application/json
,那么就是将JSON转成对象,遍历messageConverters
找到能够处理JSON的转换器,也许你已经忘了messageConverters
是怎么被注册进来的,我们来回顾一下messageConverters
的注册过程:
这里我们假定你是通过@EnableWebMvc
+实现WebMvcConfigurer
接口完成的自定义的配置:
@EnableWebMvc
注解引入了DelegatingWebMvcConfiguration
配置类,他的父类WebMvcConfigurationSupport
注册了RequestMappingHandlerAdapter
的bean
@Bean
public RequestMappingHandlerAdapter requestMappingHandlerAdapter(
@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
@Qualifier("mvcConversionService") FormattingConversionService conversionService,
@Qualifier("mvcValidator") Validator validator) {
RequestMappingHandlerAdapter adapter = createRequestMappingHandlerAdapter();
adapter.setContentNegotiationManager(contentNegotiationManager);
//这里传入MessageConverters
adapter.setMessageConverters(getMessageConverters());
。。。。省略
}
在getMessageConverters
方法中,当messageConverters
为null
的时候,初始化了一个空List准备用来装转换器,并先预留了一个抽象方法configureMessageConverters
给子类实现
protected final List<HttpMessageConverter<?>> getMessageConverters() {
if (this.messageConverters == null) {
this.messageConverters = new ArrayList<>();
//抽象方法
configureMessageConverters(this.messageConverters);
if (this.messageConverters.isEmpty()) {
addDefaultHttpMessageConverters(this.messageConverters);
}
//额外的消息转换器,可以在默认的转换器基础上进行添加
extendMessageConverters(this.messageConverters);
}
return this.messageConverters;
}
子类DelegatingWebMvcConfiguration
的setConfigurers
方法会先把我们实现WebMvcConfigurer
接口做的一些自定义配置类添加到WebMvcConfigurerComposite
中:
private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();
@Autowired(required = false)
public void setConfigurers(List<WebMvcConfigurer> configurers) {
if (!CollectionUtils.isEmpty(configurers)) {
this.configurers.addWebMvcConfigurers(configurers);
}
}
接下去看configureMessageC
onverters方法的实现,发现会在这里会处理WebMvcConfigurer
的实现类里我们所添加的MessageConverters
@Override
protected void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
this.configurers.configureMessageConverters(converters);
}
但是这个方法是覆盖性的,一旦添加了自定义的转换器,默认的转换器就不会被添加进去,所以Spring很贴心地给你提供了extendMessageConverters
,既能够保留默认的转换器又能添加自定义的,具有很强的灵活性!
创建RequestMappingHandlerAdapter
bean的时候会触发afterPropertiesSet
方法会注册一系列的默认的解析器,这时候我们所添加的消息转换器就这样被注册到当前的Processor里去了。
说完this.messageConverters
的来由之后,找到合适的转换器之后,先要处理RequestResponseBodyAdvice
这个切面的beforeBodyRead
方法,转换body里的内容转成对象之后,还要再处理一次这个切面,看看切面是不是也要对转换后的对象做一些特殊的操作,调用的是afterBodyRead
这个是由我们实现的,么有就跳过了,这部分的逻辑算是简单易懂。
当我们将body里的数据处理成对象之后,接下去同样的创建binder,把对象放进去,完成参数校验,之后还有个adaptArgumentIfNecessary
就是做了一个简单的适配转换,没什么可讲,基本上我们也不会用Optional来包裹入参,这是极不推荐的一个做法。
小结
至此我们分析了三个常见的入参解析器
RequestParamMethodArgumentResolver:处理@RequestParam
注解的参数
ServletModelAttributeMethodProcessor:处理get请求的参数并将其绑定到bean上
RequestResponseBodyMethodProcessor:处理@RequestBody
注解的参数,从body获取数据解析