Spring 注解面面通 之 @CookieValue参数绑定源码解析

  Spring MVC中使用HandlerMethodArgumentResolver策略接口来定义处理器方法参数解析器,@CookieValue使用的是ServletCookieValueMethodArgumentResolver,接下来一起来深入了解一下其源码实现。

  类结构

在这里插入图片描述

​  类解析

  HandlerMethodArgumentResolverAbstractNamedValueMethodArgumentResolver是解析策略的上层定义和抽象,AbstractCookieValueMethodArgumentResolverServletCookieValueMethodArgumentResolver类,其主要针对@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) AbstractCookieValueMethodArgumentResolverServletCookieValueMethodArgumentResolver的上层抽象,主要实现了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虽然功能和应用不是很复杂,但是有很多细节仅能从源码中才能发现,而这些细节才能决定自身的高度。

  若文中存在错误和不足,欢迎指正!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值