Spring MVC中使用HandlerMethodArgumentResolver
策略接口来定义处理器方法参数解析器,@CookieValue
使用的是ServletCookieValueMethodArgumentResolver
,接下来一起来深入了解一下其源码实现。
类结构
类解析
HandlerMethodArgumentResolver
和AbstractNamedValueMethodArgumentResolver
是解析策略的上层定义和抽象,AbstractCookieValueMethodArgumentResolver
和ServletCookieValueMethodArgumentResolver
类,其主要针对@CookieValue
进行的参数解析实现。
1) HandlerMethodArgumentResolver
主要定义策略接口,包括supportsParameter(...)
和resolveArgument(...)
两个方法,方法具体含义可参照如下的源码注释。
package org.springframework.web.method.support;
import org.springframework.core.MethodParameter;
import org.springframework.lang.Nullable;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
/**
* 给定请求上下文中进行方法参数解析的策略接口.
*/
public interface HandlerMethodArgumentResolver {
/**
* 当前解析器是否支持给定的MethodParameter.
* @param parameter 待检查的方法参数.
* @return 此解析器支持给定的方法参数解析,返回true,否则返回false.
*/
boolean supportsParameter(MethodParameter parameter);
/**
* 给定请求中解析方法参数的参数值.
* ModelAndViewContainer:提供对请求模型的访问.
* WebDataBinderFactory:提供了待数据绑定和类型转换时创建WebDataBinder实例的方法.
* @param parameter 待解析的方法参数,此参数必须先调用supportsParameter(...)方法,并返回true.
* @param mavContainer 当前请求的ModelAndViewContainer.
* @param webRequest 当前请求实例.
* @param binderFactory 用于创建WebDataBinder实例的工厂.
* @return 已解析的参数值,如果不可解析,则为null.
*/
@Nullable
Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;
}
2) AbstractNamedValueMethodArgumentResolver
用于定义上层解析逻辑,利用经典的模板设计模式,定义算法骨架,再由子类进行具体的细节补充。
① 此类是名值解析的抽象基类,例如:请求参数、请求头和路径变量,每个名值都包含一个名称、一个是否必需标志和一个默认值。
② 子类需在此类已定义的算法骨架中进行补充:
· 实现获取方法参数名值的方法。
· 实现名值解析为参数值的方法。
· 实现参数值缺失时的处理方法。
· 实现可选的已解析值的处理方法。
③ 名称和默认值可以包含${…}
占位符和Spring表达式语言#{…}
表达式,为保证正常处理这种情况,必须向类构造函数提供ConfigurableBeanFactory
。
④ 若解析后的参数值与方法参数类型不匹配,则需使用WebDataBinderFactory
创建WebDataBinder
将类型转换为方法参数匹配的参数值。
package org.springframework.web.method.annotation;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import javax.servlet.ServletException;
import org.springframework.beans.ConversionNotSupportedException;
import org.springframework.beans.TypeMismatchException;
import org.springframework.beans.factory.config.BeanExpressionContext;
import org.springframework.beans.factory.config.BeanExpressionResolver;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.core.MethodParameter;
import org.springframework.lang.Nullable;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.ValueConstants;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.RequestScope;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
/**
* 用于名值解析的抽象基类.
* 例如:请求参数、请求头和路径变量均是名值的示例.
* 每个名值都可以有一个名称、一个必需的标志和一个默认值.
*
* 子类需定义如何执行以下操作:
* 1.获取方法参数的命名值信息.
* 2.将名称解析为参数值.
* 3.当需要参数值时,处理缺少的参数值.
* 4.可选地处理已解析的值.
*
* 默认值字符串可以包含${…}占位符和Spring表达式语言#{…}表达式.
* 要使其工作,必须向类构造函数提供ConfigurableBeanFactory.
*
* 如果解析的参数值与方法参数类型不匹配,则WebDataBinderFactory创建WebDataBinder将类型转换为方法参数匹配的参数值.
*/
public abstract class AbstractNamedValueMethodArgumentResolver implements HandlerMethodArgumentResolver {
/**
* BeanFactory配置接口.
*/
@Nullable
private final ConfigurableBeanFactory configurableBeanFactory;
/**
* Bean表达式上下文.
*/
@Nullable
private final BeanExpressionContext expressionContext;
/**
* 名值解析缓存.
*/
private final Map<MethodParameter, NamedValueInfo> namedValueInfoCache = new ConcurrentHashMap<>(256);
/**
* Constructor.
*/
public AbstractNamedValueMethodArgumentResolver() {
this.configurableBeanFactory = null;
this.expressionContext = null;
}
/**
* Constructor.
* @param beanFactory 一个Bean工厂,用于解析默认值中的${…}占位符和#{…}SpEL表达式,如果默认值不包含表达式,则使用null.
*/
public AbstractNamedValueMethodArgumentResolver(@Nullable ConfigurableBeanFactory beanFactory) {
this.configurableBeanFactory = beanFactory;
this.expressionContext =
(beanFactory != null ? new BeanExpressionContext(beanFactory, new RequestScope()) : null);
}
/**
* 参数解析方法.
*/
@Override
@Nullable
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
// 获取方法参数的名值信息.
NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
// 处理java.util.Optional情况.
MethodParameter nestedParameter = parameter.nestedIfOptional();
// 解析名称的值,包含${…}占位符和Spring表达式语言#{…}表达式.
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) {
// 若未解析到任何值,则转而处理默认值,默认值包含${…}占位符和Spring表达式语言#{…}表达式.
if (namedValueInfo.defaultValue != null) {
arg = resolveStringValue(namedValueInfo.defaultValue);
}
// 若设置required=true且方法参数非java.util.Optional类型,则有具体实现类抛出异常.
else if (namedValueInfo.required && !nestedParameter.isOptional()) {
handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
}
// 若required=false且未设置默认值,则根据方法参数类型返回对应的空值.
arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
}
else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
arg = resolveStringValue(namedValueInfo.defaultValue);
}
// WebDataBinderFactory处理.
if (binderFactory != null) {
// 获取WebDataBinder实例.
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;
}
/**
* 获取给定方法参数的名值信息.
*/
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;
}
/**
* 为给定的方法参数创建NamedValueInfo对象.
* 实现通常通过MethodParameter.getParameterAnnotation(Class)检索方法注解.
* @param parameter 方法参数.
* @return 名值信息.
*/
protected abstract NamedValueInfo createNamedValueInfo(MethodParameter parameter);
/**
* 基于给定的NamedValueInfo创建一个新的NamedValueInfo,其中包含经过清理的值.
*/
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);
}
/**
* 解析给定的注解指定值,该值可能包含占位符和表达式.
*/
@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);
}
/**
* 将给定值名称解析为参数值.
* 由具体的实现类实现.
* @param name 待解析的值名称.
* @param parameter 待解析为参数值的方法参数(如果是java.util.Optional声明,则为预嵌套).
* @param request 当前请求.
* @return 已解析的参数(可能为null).
*/
@Nullable
protected abstract Object resolveName(String name, MethodParameter parameter, NativeWebRequest request)
throws Exception;
/**
* 当名值是必需时,resolveName(String, MethodParameter, NativeWebRequest)返回null且未指定默认值,子类通常会抛出异常.
* @param name 值名称.
* @param parameter 方法参数.
* @param request 当前请求.
*/
protected void handleMissingValue(String name, MethodParameter parameter, NativeWebRequest request)
throws Exception {
handleMissingValue(name, parameter);
}
/**
* 当名值是必需时,resolveName(String, MethodParameter, NativeWebRequest)返回null且未指定默认值,子类通常会抛出异常.
* @param name 值名称.
* @param parameter 方法参数.
*/
protected void handleMissingValue(String name, MethodParameter parameter) throws ServletException {
throw new ServletRequestBindingException("Missing argument '" + name +
"' for method parameter of type " + parameter.getNestedParameterType().getSimpleName());
}
/**
* 处理空值的情况.
*/
@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;
}
/**
* 在解析值后调用.
* @param arg 解析的参数值.
* @param name 参数名称.
* @param parameter 参数类型.
* @param mavContainer ModelAndViewContainer,可能为null.
* @param webRequest 当前请求.
*/
protected void handleResolvedValue(@Nullable Object arg, String name, MethodParameter parameter,
@Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest) {
}
/**
* 名值定义,包括名称、是否必需以及默认值.
*/
protected static class NamedValueInfo {
/**
* 绑定名称.
*/
private final String name;
/**
* 是否必需.
*/
private final boolean required;
/**
* 默认值.
*/
@Nullable
private final String defaultValue;
/**
* Constructor.
*/
public NamedValueInfo(String name, boolean required, @Nullable String defaultValue) {
this.name = name;
this.required = required;
this.defaultValue = defaultValue;
}
}
}
3) AbstractCookieValueMethodArgumentResolver
是ServletCookieValueMethodArgumentResolver
的上层抽象,主要实现了supportsParameter(...)
、createNamedValueInfo(...)
和handleMissingValue(...)
方法,都是最基本的方法,可以参照如下的源码注释。
package org.springframework.web.method.annotation;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.core.MethodParameter;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.annotation.CookieValue;
/**
* 用于解析@CookieValue注释的方法参数的抽象基类,子类从请求中提取Cookie的值.
*
* @CookieValue 是从Cookie解析的命名值.
* 当Cookie不存在时,它有一个必需的标志和一个默认值.
*
* 可以调用WebDataBinder将类型转换应用于解析的Cookie值.
*/
public abstract class AbstractCookieValueMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver {
/**
* @param beanFactory 用于解析默认值中${…}占位符和#{…}SpEL表达式的Bean工厂.
* 如果默认值不包含表达式则为null.
*/
public AbstractCookieValueMethodArgumentResolver(@Nullable ConfigurableBeanFactory beanFactory) {
super(beanFactory);
}
/**
* 检查方法参数是否包含@CookieValue.
*/
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(CookieValue.class);
}
/**
* CookieValueNamedValueInfo.
*/
@Override
protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
CookieValue annotation = parameter.getParameterAnnotation(CookieValue.class);
Assert.state(annotation != null, "No CookieValue annotation");
return new CookieValueNamedValueInfo(annotation);
}
/**
* 处理Cookie缺失的异常.
*/
@Override
protected void handleMissingValue(String name, MethodParameter parameter) throws ServletRequestBindingException {
throw new ServletRequestBindingException("Missing cookie '" + name +
"' for method parameter of type " + parameter.getNestedParameterType().getSimpleName());
}
/**
* CookieValueNamedValueInfo.
*/
private static class CookieValueNamedValueInfo extends NamedValueInfo {
private CookieValueNamedValueInfo(CookieValue annotation) {
super(annotation.name(), annotation.required(), annotation.defaultValue());
}
}
}
4) ServletCookieValueMethodArgumentResolver
实现了最主要的resolveName(...)
方法,用于进行参数解析。
① Cookie
使用WebUtils.getCookie(...)
从NativeWebRequest
中解析。
② 若方法参数是Cookie
类型,那么直接返回Cookie
类型。
③ 若方法参数非Cookie
类型,那么urlPathHelper.decodeRequestString(...)
解析得到String
类型的值。
package org.springframework.web.servlet.mvc.method.annotation;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.core.MethodParameter;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.annotation.AbstractCookieValueMethodArgumentResolver;
import org.springframework.web.util.UrlPathHelper;
import org.springframework.web.util.WebUtils;
/**
* org.springframework.web.method.annotation.AbstractCookieValueMethodArgumentResolver的实现.
* 用于从HttpServletRequest解析Cookie的值.
*/
public class ServletCookieValueMethodArgumentResolver extends AbstractCookieValueMethodArgumentResolver {
/**
* URL路径匹配帮助类.
*/
private UrlPathHelper urlPathHelper = new UrlPathHelper();
/**
* Constructor.
*/
public ServletCookieValueMethodArgumentResolver(@Nullable ConfigurableBeanFactory beanFactory) {
super(beanFactory);
}
/**
* urlPathHelper setter.
*/
public void setUrlPathHelper(UrlPathHelper urlPathHelper) {
this.urlPathHelper = urlPathHelper;
}
/**
* 根据名称解析参数值.
*/
@Override
@Nullable
protected Object resolveName(String cookieName, MethodParameter parameter,
NativeWebRequest webRequest) throws Exception {
HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
Assert.state(servletRequest != null, "No HttpServletRequest");
// 从NativeWebRequest中获取请求的Cookie.
Cookie cookieValue = WebUtils.getCookie(servletRequest, cookieName);
// 方法参数是Cookie类型,直接返回获取的Cookie实例.
if (Cookie.class.isAssignableFrom(parameter.getNestedParameterType())) {
return cookieValue;
}
// 从请求中直接获取Cookie的值.
else if (cookieValue != null) {
return this.urlPathHelper.decodeRequestString(servletRequest, cookieValue.getValue());
}
else {
return null;
}
}
}
总结
知其所以然,才能运用自如,@CookieValue
虽然功能和应用不是很复杂,但是有很多细节仅能从源码中才能发现,而这些细节才能决定自身的高度。
若文中存在错误和不足,欢迎指正!