1、HandlerMethodArgumentResolver接口
HandlerMethodArgumentResolver接口及其实现类主要是为了实现解析处理器中处理器request的方法中的参数。因为前面提到的HandlerMethod类型的处理器,参数可以是任意的形式,所以这就为定义参数解析器带来的很大的复杂度,在Spring MVC中提供了数量庞大的参数解析器,类图如下:
因为HandlerMethodArgumentResolver家族非常庞大,所以查看类图时,需要放大或下载到本地再查看。
虽然,HandlerMethodArgumentResolver家族比较庞大,但是逻辑还是比较简单的,只是针对不同类型的参数,分别提供了对应的参数解析器而已。在HandlerMethodArgumentResolver家族中,可以分成四类:
- XXXMethodArgumentResolver
表示一个参数解析器,只处理方法的参数。 - XXXMethodProcessor
表示除了是一个参数解析器外,还是一个返回值解析器。 - HandlerMethodArgumentResolverComposite
这是一个特殊的参数解析器,它不具体解析参数,而是可以将多个别的解析器包含在其中,解析时调用其所包含的解析器具体解析参数。 - AbstractWebArgumentResolverAdapter系列
主要做适配器使用,实现WebArgumentResolver解析器与HandlerMethodArgumentResolver解析器的适配工作。
2、HandlerMethodArgumentResolverComposite类
这是一个特殊的参数解析器,它不具体解析参数,而是在类中定义了一个集合类的属性,然后把所有注册的其他解析器都存放到这个属性中,然后使用这个类进行解析参数的时候,实际上就交给了集合中的那些解析器进行处理参数,具体的做法,我们下面详细分析。
在HandlerMethodArgumentResolverComposite类中,为了保存其他真是用来解析参数的集合,所以定义了一个argumentResolvers 参数,为了缓存参数类型与参数解析器的映射关系,又定义了一个argumentResolverCache属性,具体代码如下:
private final List<HandlerMethodArgumentResolver> argumentResolvers = new LinkedList<>();
private final Map<MethodParameter, HandlerMethodArgumentResolver> argumentResolverCache = new ConcurrentHashMap<>(256);
然后,该类还提供了为该属性添加解析器、获取该类的所有解析器、清除解析的方法,器本质就是操作argumentResolvers,进行元素的添加、删除或获取,逻辑非常简单,不再贴出代码了。
最后,开始分析实现的接口方法。首先分析supportsParameter()方法的实现,该方法是通过循环遍历argumentResolvers集合中的解析器是否有满足需要的,如果有的话说明支持该类型参数的解析 ,如果没有对应的参数,说明不支持该类型参数的解析。代码如下:
@Override
public boolean supportsParameter(MethodParameter parameter) {
return getArgumentResolver(parameter) != null;
}
@Nullable
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
//判断缓存中是否有对应的解析器
HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
//没有的话,再从argumentResolvers中获取
if (result == null) {
//遍历符合要求的解析器,如果有符合要求的就放到缓存argumentResolverCache属性里,并赋值给局部变量result中,然后结束循环
for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
if (resolver.supportsParameter(parameter)) {
result = resolver;
this.argumentResolverCache.put(parameter, result);
break;
}
}
}
return result;
}
然后,实现了解析方法resolveArgument(),该方法就是从属性argumentResolvers中获取匹配的参数解析器,然后交给这个解析器,进行参数的解析,具体实现如下:
@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);
}
3、AbstractWebArgumentResolverAdapter类
主要做参数解析适配器使用,实现WebArgumentResolver系列的解析器与HandlerMethodArgumentResolver解析器的适配工作,其中WebArgumentResolver接口主要为用户提供了自定义解析的入口。WebArgumentResolver接口如下:
@FunctionalInterface
public interface WebArgumentResolver {
//表示不支持该类型参数的解析
Object UNRESOLVED = new Object();
//参数解析方法定义
@Nullable
Object resolveArgument(MethodParameter methodParameter, NativeWebRequest webRequest) throws Exception;
}
为了实现自定义参数解析器与HandlerMethodArgumentResolver参数解析器的适配,定义了AbstractWebArgumentResolverAdapter父类和ServletWebArgumentResolverAdapter子类。通过定义一个代表自定义解析器的属性adaptee这种组合方式,在实现HandlerMethodArgumentResolver接口的两个方法中,实际上使用自定义解析器来进行处理。具体实现如下:
//代表自定义参数解析器
private final WebArgumentResolver adaptee;
//判断是否支持该类型参数的解析
@Override
public boolean supportsParameter(MethodParameter parameter) {
try {
//获取请求的NativeWebRequest参数,在子类ServletWebArgumentResolverAdapter中实现。
NativeWebRequest webRequest = getWebRequest();
Object result = this.adaptee.resolveArgument(parameter, webRequest);
if (result == WebArgumentResolver.UNRESOLVED) {//如果解析结果是UNRESOLVED,则判断不支持该类型参数解析
return false;
}
else {//判断通过WebArgumentResolver实现类的resolveArgument()方法封装的参数,是否和当前参数匹配
return ClassUtils.isAssignableValue(parameter.getParameterType(), result);
}
}
catch (Exception ex) {
// ignore (see class-level doc)
if (logger.isDebugEnabled()) {
logger.debug("Error in checking support for parameter [" + parameter + "]: " + ex.getMessage());
}
return false;
}
}
//解析参数
@Override
@Nullable
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
Class<?> paramType = parameter.getParameterType();
Object result = this.adaptee.resolveArgument(parameter, webRequest);
if (result == WebArgumentResolver.UNRESOLVED || !ClassUtils.isAssignableValue(paramType, result)) {//不符合要求,直接抛出异常
throw new IllegalStateException(
"Standard argument type [" + paramType.getName() + "] in method " + parameter.getMethod() +
"resolved to incompatible value of type [" + (result != null ? result.getClass() : null) +
"]. Consider declaring the argument type in a less specific fashion.");
}
return result;
}
//子类ServletWebArgumentResolverAdapter中的方法
@Override
protected NativeWebRequest getWebRequest() {
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
Assert.state(requestAttributes instanceof ServletRequestAttributes, "No ServletRequestAttributes");
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) requestAttributes;
return new ServletWebRequest(servletRequestAttributes.getRequest());
}
4、XXXMethodProcessor系列
XXXMethodProcessor系列表示既是一个参数解析器,还是一个返回值解析器。所以这类解析器同时实现了HandlerMethodArgumentResolver和HandlerMethodReturnValueHandler两个接口,我们这里以ModelMethodProcessor类为例,分析这类解析器的实现方式。因为ModelMethodProcessor类明确就是解析Model类型的参数和返回值,所以仅仅实现两个接口的四个方法即可。具体实现如下:
public class ModelMethodProcessor implements HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler {
//判断是否是Model类型参数
@Override
public boolean supportsParameter(MethodParameter parameter) {
return Model.class.isAssignableFrom(parameter.getParameterType());
}
//解析Model类型的参数,通过ModelAndViewContainer获取其中存储的Model参数即可。
@Override
@Nullable
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
Assert.state(mavContainer != null, "ModelAndViewContainer is required for model exposure");
return mavContainer.getModel();
}
//判断返回值是否是Model类型
@Override
public boolean supportsReturnType(MethodParameter returnType) {
return Model.class.isAssignableFrom(returnType.getParameterType());
}
//处理Model类型参数,并把该参数添加到ModelAndViewContainer中
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
if (returnValue == null) {
return;
}
else if (returnValue instanceof Model) {
mavContainer.addAllAttributes(((Model) returnValue).asMap());
}
else {
// should not happen
throw new UnsupportedOperationException("Unexpected return type: " +
returnType.getParameterType().getName() + " in method: " + returnType.getMethod());
}
}
}
5、XXXMethodArgumentResolver系列
XXXMethodArgumentResolver系列表示仅是一个参数解析器,只处理方法的参数。在这类处理器中,只需要实现HandlerMethodArgumentResolver接口即可。这里我们以用来解析@PathVariable注解参数的PathVariableMethodArgumentResolver解析器为例。
PathVariableMethodArgumentResolver解析器,是通过继承AbstractNamedValueMethodArgumentResolver抽象类实现了对@PathVariable注解参数的解析。AbstractNamedValueMethodArgumentResolver抽象类实现抽象命名值(named value)控制器方法参数解析的逻辑,它实现了命名值控制器方法解析的主逻辑,也就是对接口HandlerMethodArgumentResolver所定义的方法resolveArgument提供了实现并且通过关键字final禁止覆盖。但同时也要求具体实现子类实现如下方法以实现特定的逻辑 :
- boolean supportsParameter(MethodParameter parameter) 该方法在HandlerMethodArgumentResolver接口中定义,且在该抽象类中没有提供实现,即需要子类实现
- protected abstract NamedValueInfo createNamedValueInfo(MethodParameter parameter); 该方法是在AbstractNamedValueMethodArgumentResolver抽象类中定义的抽象方法,主要用来获取该方法参数的命名值元数据:名称字符串,是否必要参数,缺省值。通常是通过获取该参数上的注解信息获得的。
- protected abstract Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception; 该方法是在AbstractNamedValueMethodArgumentResolver抽象类中定义的抽象方法,主要用来根据参数名字name从请求上下文中解析出参数值,返回值虽然使用Object,但绝大多数情况下是实际类型是字符串。
5.1、AbstractNamedValueMethodArgumentResolver抽象类
属性字段:
//当前Spring容器
@Nullable
private final ConfigurableBeanFactory configurableBeanFactory;
//使用bean定义进行表达式求值的上下文对象
@Nullable
private final BeanExpressionContext expressionContext;
//用于缓存找到的命名值信息
private final Map<MethodParameter, NamedValueInfo> namedValueInfoCache = new ConcurrentHashMap<>(256);
resolveArgument()方法:
@Override
@Nullable
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
//获取方法参数 parameter 命名值描述信息 namedValueInfo
NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
MethodParameter nestedParameter = parameter.nestedIfOptional();
//参数的名称可能是包含表达式,这里就是通过resolveStringValue方法获取真正的参数名
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);
}
//处理NULL参数值
arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
}
else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
arg = resolveStringValue(namedValueInfo.defaultValue);
}
//尝试把参数转为目标对象类型
if (binderFactory != null) {
//使用 binderFactory 数据绑定工厂创建数据绑定器,用于数据类型的转换
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;
}
&esmp;在上面方法中,通过调用getNamedValueInfo()方法,实现获取方法参数 parameter 命名值描述信息 namedValueInfo,具体实现如下:
private NamedValueInfo getNamedValueInfo(MethodParameter parameter) {
//判断缓存是否有该参数的NamedValueInfo
NamedValueInfo namedValueInfo = this.namedValueInfoCache.get(parameter);
if (namedValueInfo == null) {//如果没有
//抽象方法,由子类实现,创建对应的NamedValueInfo 实例
namedValueInfo = createNamedValueInfo(parameter);
//处理namedValueInfo,生成一个新的可安全使用的namedValueInfo 实例
namedValueInfo = updateNamedValueInfo(parameter, namedValueInfo);
this.namedValueInfoCache.put(parameter, namedValueInfo);
}
return namedValueInfo;
}
//主要根据已有的NamedValueInfo对象重新创建一个新的NamedValueInfo对象。同时处理掉默认值defaultValue中的特殊字符
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);
}
通过上面的分析,我们知道在该类中提供了两个抽象方法createNamedValueInfo()方法和resolveName()方法交由子类实现,再加上接口中的supportsParameter()方法,一共有三个方法需要子类实现,即对应了前面的描述。我们下面开始分析实现类PathVariableMethodArgumentResolver是如何实现这三个方法,并实现对@PathVariable注解的参数进行解析的。
5.2、PathVariableMethodArgumentResolver类
PathVariableMethodArgumentResolver类除了继承至AbstractNamedValueMethodArgumentResolver抽象类外,还实现了UriComponentsContributor接口。UriComponentsContributor接口提供了两个方法,其中一个还与HandlerMethodArgumentResolver接口中的supportsParameter()方法一样,即PathVariableMethodArgumentResolver类实现一个supportsParameter方法,相当于完成了两个接口该方法的实现,另外一个主要用于构建UriComponents相关内容。
supportsParameter方法
//判断是否是符合要求的@PathVariable注解的参数类型
@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;
}
createNamedValueInfo()方法
//创建PathVariableNamedValueInfo 类型的NamedValueInfo 实例
@Override
protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
PathVariable ann = parameter.getParameterAnnotation(PathVariable.class);
Assert.state(ann != null, "No PathVariable annotation");
return new PathVariableNamedValueInfo(ann);
}
//内部类
private static class PathVariableNamedValueInfo extends NamedValueInfo {
public PathVariableNamedValueInfo(PathVariable annotation) {
super(annotation.name(), annotation.required(), ValueConstants.DEFAULT_NONE);
}
}
resolveName()方法
//解析参数名对应的参数值
@Override
@SuppressWarnings("unchecked")
@Nullable
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
Map<String, String> uriTemplateVars = (Map<String, String>) request.getAttribute(
HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
return (uriTemplateVars != null ? uriTemplateVars.get(name) : null);
}
handleResolvedValue()方法
//在父类中,该方法是空方法,这里增加了修改参数的逻辑。主要实现为request设置key为"org.springframework.web.servlet.View.pathVariables"的属性值。
@Override
@SuppressWarnings("unchecked")
protected void handleResolvedValue(@Nullable Object arg, String name, MethodParameter parameter,
@Nullable ModelAndViewContainer mavContainer, NativeWebRequest request) {
String key = View.PATH_VARIABLES;
int scope = RequestAttributes.SCOPE_REQUEST;
Map<String, Object> pathVars = (Map<String, Object>) request.getAttribute(key, scope);
if (pathVars == null) {
pathVars = new HashMap<>();
request.setAttribute(key, pathVars, scope);
}
pathVars.put(name, arg);
}
关于UriComponentsContributor接口的实现类,本节没有分析,后续的章节再详细分析相关的知识点。