Spring MVC学习笔记之Spring MVC组件HandlerMethodArgumentResolver

导读:本篇文章讲解 Spring MVC学习笔记之Spring MVC组件HandlerMethodArgumentResolver,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

1、HandlerMethodArgumentResolver接口

  HandlerMethodArgumentResolver接口及其实现类主要是为了实现解析处理器中处理器request的方法中的参数。因为前面提到的HandlerMethod类型的处理器,参数可以是任意的形式,所以这就为定义参数解析器带来的很大的复杂度,在Spring MVC中提供了数量庞大的参数解析器,类图如下:
在这里插入图片描述

因为HandlerMethodArgumentResolver家族非常庞大,所以查看类图时,需要放大或下载到本地再查看。

  虽然,HandlerMethodArgumentResolver家族比较庞大,但是逻辑还是比较简单的,只是针对不同类型的参数,分别提供了对应的参数解析器而已。在HandlerMethodArgumentResolver家族中,可以分成四类:

  1. XXXMethodArgumentResolver
    表示一个参数解析器,只处理方法的参数。
  2. XXXMethodProcessor
    表示除了是一个参数解析器外,还是一个返回值解析器。
  3. HandlerMethodArgumentResolverComposite
    这是一个特殊的参数解析器,它不具体解析参数,而是可以将多个别的解析器包含在其中,解析时调用其所包含的解析器具体解析参数。
  4. 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接口的实现类,本节没有分析,后续的章节再详细分析相关的知识点。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/68794.html

(0)
小半的头像小半

相关推荐

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!