概述
Spring Web
使用AbstractNamedValueMethodArgumentResolver
抽象命名值(named value
)控制器方法参数解析的逻辑。像是一般意义上我们通过POST FORM
方式传递的参数,GET URL
中通过QueryString
传递的参数,请求的头部信息,URL
路径变量这种命名了的参数,都可以称作"命名值"(named value
)。
AbstractNamedValueMethodArgumentResolver
是一个抽象类,它实现了命名值控制器方法解析的主逻辑,也就是对接口HandlerMethodArgumentResolver
所定义的方法resolveArgument
提供了实现并且通过关键字final
禁止覆盖。但同时也要求具体实现子类实现如下方法以实现特定的逻辑 :
boolean supportsParameter(MethodParameter parameter)
当前实现是否支持解析该方法参数
NamedValueInfo createNamedValueInfo(MethodParameter parameter)
获取该方法参数的命名值元数据:名称字符串,是否必要参数,缺省值。通常是通过获取该参数上的注解信息获得的。
Object resolveName(String name, MethodParameter parameter, NativeWebRequest request)
根据参数名字
name
从请求上下文中解析出参数值,返回值虽然使用Object
,但绝大多数情况下是实际类型是字符串。
Spring
框架内置提供了各种AbstractNamedValueMethodArgumentResolver
实现子类,除非很特殊的需求,否则应该都能够被满足。AbstractNamedValueMethodArgumentResolver
及其实现子类的继承关系如下所示 :
- jar 包
org.springframework:spring-web:5.1.5.RELEASE
- java 包
org.springframework.web.method.annotation
AbstractCookieValueMethodArgumentResolver
ServletCookieValueMethodArgumentResolver
ExpressionValueMethodArgumentResolver
RequestHeaderMethodArgumentResolver
RequestParamMethodArgumentResolver
- java 包
- jar 包
org.springframework:spring-webmvc:5.1.5.RELEASE
- java 包
org.springframework.web.servlet.mvc.method.annotation
MatrixVariableMethodArgumentResolver
PathVariableMethodArgumentResolver
RequestAttributeMethodArgumentResolver
SessionAttributeMethodArgumentResolver
- java 包
源代码
源代码版本
Spring Web 5.1.5.RELEASE
package org.springframework.web.method.annotation;
// 省略 import 行
public abstract class AbstractNamedValueMethodArgumentResolver
implements HandlerMethodArgumentResolver {
// 记录当前 Spring IoC 容器
@Nullable
private final ConfigurableBeanFactory configurableBeanFactory;
// 使用bean定义进行表达式求值的上下文对象
@Nullable
private final BeanExpressionContext expressionContext;
// 用于缓存找到的命名值信息
private final Map<MethodParameter, NamedValueInfo> namedValueInfoCache
= new ConcurrentHashMap<>(256);
public AbstractNamedValueMethodArgumentResolver() {
this.configurableBeanFactory = null;
this.expressionContext = null;
}
/**
* Create a new {@link AbstractNamedValueMethodArgumentResolver} instance.
* @param beanFactory a bean factory to use for resolving ${...} placeholder
* and #{...} SpEL expressions in default values, or {@code null} if default
* values are not expected to contain expressions
*/
public AbstractNamedValueMethodArgumentResolver(@Nullable ConfigurableBeanFactory beanFactory) {
this.configurableBeanFactory = beanFactory;
// 初始化使用bean定义进行表达式求值的上下文对象
// 仅在 this.configurableBeanFactory 被设置为非 null 时真正做初始化
this.expressionContext =
(beanFactory != null
? new BeanExpressionContext(beanFactory, new RequestScope())
: null);
}
// 接口 HandlerMethodArgumentResolver 所定义的方法,解析参数
// 1. parameter 是要解析参数值的控制器方法参数
// 2. mavContainer 是一个容器对象,用来保存控制器方法执行过程中各种决定信息,本方法并未使用它
// 3. webRequest 包装了当前请求对象和响应对象,并且能返回 Servlet 规范定义的请求和响应对象
// 4. binderFactory 是 数据绑定器工厂,用于生成数据绑定器,
@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();
// namedValueInfo.name 是参数的名称字符串,不过该字符串可能是个表达式,需要进一步解析为
// 最终的参数名称,下面的 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) {
// 没能从请求上下文中分析得到参数值的分支 ,也就是 arg 为 null 的情况
if (namedValueInfo.defaultValue != null) {
// 对该参数指定了缺省值的情况
// 尝试应用缺省值,缺省值通过对 namedValueInfo.defaultValue 进行表达式求值得到
arg = resolveStringValue(namedValueInfo.defaultValue);
}
else if (namedValueInfo.required && !nestedParameter.isOptional()) {
// 没能从请求上下文中分析得到参数值,并且没有指定缺省值, 并且被指定为必要参数的情况的处理
// 也可以概括为必要参数的值缺失情况的处理
// 参考注解 @RequestParam 的属性 required
// AbstractNamedValueMethodArgumentResolver 这里的缺省实现是抛出异常 :
// ServletRequestBindingException
handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
}
// 对 null 参数值的处理 :
// 1. 如果参数类型为 Boolean 或者 boolean, 则 null 被理解成 Boolean.FALSE ;
// 2. 如果参数类型为其他 Java 基本数据类型,则抛出异常 IllegalStateException,
// 因为一个除了 boolean 之外的其他基本数据类型,是无法接受一个 null 值的 ;
arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
}
else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
// arg 为0长度字符串并且有缺省值可以应用的情况的处理
arg = resolveStringValue(namedValueInfo.defaultValue);
}
// 现在, arg 变量值是经过各种解析将要应用到相应控制器方法参数的参数值,
// 但是,从请求上下文中分析得到的 arg 通常是字符串类型,该类型跟目标控制器方法参数的类型
// 通常都是不同的,所以现在需要尝试从值 arg 转换得到目标控制器方法参数类型的对象,
// 例子 :
// 1. arg 为字符串 "1" , 目标类型为 int, 则转换后的 arg 会是整数 1
// 2. arg 为字符串 "test", 目标类型为 int, 则转换失败,抛出异常 TypeMismatchException
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());
}
}
// 上面是从请求中分析得到的参数值向目标控制器方法参数进行类型转换的逻辑,如果木有出现异常,
// 程序执行会走到这里,此时 arg 对象是类型为目标控制器方法参数类型的对象,该值可以应用到
// 目标方法参数了。
// 在将 arg 返回给调用者真正应用到目标方法参数之前,当前对象还可以通过 handleResolvedValue
// 方法对 arg 做一番调整。不过在抽象类 AbstractNamedValueMethodArgumentResolver 中,
// 此方法是一个空方法,也就是不做任何调整,但是具体实现子类可以覆盖实现该方法实现自己的逻辑。
handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);
// 从请求上下文中分析参数值,应用缺省值,处理 null 值,源类型到目标类型的类型转换都已经做完了,
// 大功告成,将此分析得到的参数值 arg 返回给调用者让其继续接下来的后续逻辑
return arg;
}
/**
* Obtain the named value for the given method parameter.
*/
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;
}
/**
* Create the {@link NamedValueInfo} object for the given method parameter.
* Implementations typically retrieve the method annotation by means of
* {@link MethodParameter#getParameterAnnotation(Class)}.
* @param parameter the method parameter
* @return the named value information
*/
protected abstract NamedValueInfo createNamedValueInfo(MethodParameter parameter);
/**
* Create a new NamedValueInfo based on the given NamedValueInfo with sanitized values.
*/
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);
}
/**
* Resolve the given annotation-specified value,
* potentially containing placeholders and expressions.
*/
@Nullable
private Object resolveStringValue(String value) {
if (this.configurableBeanFactory == null) {
return value;
}
String placeholdersResolved = this.configurableBeanFactory.resolveEmbeddedValue(value);
BeanExpressionResolver exprResolver =
this.configurableBeanFactory.getBeanExpressionResolver();
if (exprResolver == null || this.expressionContext == null) {
return value;
}
return exprResolver.evaluate(placeholdersResolved, this.expressionContext);
}
/**
* Resolve the given parameter type and value name into an argument value.
* @param name the name of the value being resolved
* @param parameter the method parameter to resolve to an argument value
* (pre-nested in case of a {@link java.util.Optional} declaration)
* @param request the current request
* @return the resolved argument (may be {@code null})
* @throws Exception in case of errors
*/
@Nullable
protected abstract Object resolveName(String name, MethodParameter parameter,
NativeWebRequest request)
throws Exception;
/**
* Invoked when a named value is required, but {@link #resolveName(String, MethodParameter,
* NativeWebRequest)}
* returned {@code null} and there is no default value. Subclasses typically throw an exception
* in this case.
* @param name the name for the value
* @param parameter the method parameter
* @param request the current request
* @since 4.3
*/
protected void handleMissingValue(String name, MethodParameter parameter,
NativeWebRequest request) throws Exception {
handleMissingValue(name, parameter);
}
/**
* Invoked when a named value is required, but {@link #resolveName(String, MethodParameter,
* NativeWebRequest)}
* returned {@code null} and there is no default value. Subclasses typically throw an exception
* in this case.
* @param name the name for the value
* @param parameter the method parameter
*/
protected void handleMissingValue(String name, MethodParameter parameter)
throws ServletException {
throw new ServletRequestBindingException("Missing argument '" + name +
"' for method parameter of type " + parameter.getNestedParameterType().getSimpleName());
}
/**
* A {@code null} results in a {@code false} value for {@code boolean}s or an exception
* for other primitives.
*/
@Nullable
private Object handleNullValue(String name, @Nullable Object value, Class<?> paramType) {
if (value == null) {
if (Boolean.TYPE.equals(paramType)) {
return Boolean.FALSE;
}
else if (paramType.isPrimitive()) {
throw new IllegalStateException("Optional " + paramType.getSimpleName()
+ " parameter '" + name +
"' is present but cannot be translated into a null value due to being declared as a " +
"primitive type. Consider declaring it as object wrapper for the corresponding primitive type.");
}
}
return value;
}
/**
* Invoked after a value is resolved.
* @param arg the resolved argument value
* @param name the argument name
* @param parameter the argument parameter type
* @param mavContainer the {@link ModelAndViewContainer} (may be {@code null})
* @param webRequest the current request
*/
protected void handleResolvedValue(@Nullable Object arg, String name, MethodParameter parameter,
@Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest) {
// 对解析后的参数值进行处理
// 当前实现是一个空方法,不过具体实现子类可以覆盖实现该方法以实现逻辑定制
}
/**
* Represents the information about a named value, including name, whether it's required and
* a default value.
* 一个关于命名值的元数据描述类,包含如下信息 :
* 1. 原始命名字符串(可能是一个需要求值的表达式)
* 2. 该参数是否必要
* 3. 该参数的参数值缺失时要应用的缺省值字符串
*/
protected static class NamedValueInfo {
private final String name;
private final boolean required;
@Nullable
private final String defaultValue;
public NamedValueInfo(String name, boolean required, @Nullable String defaultValue) {
this.name = name;
this.required = required;
this.defaultValue = defaultValue;
}
}
}