欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2022-06-18 12:23:22
  Spring MVC中使用HandlerMethodArgumentResolver策略接口来定义处理器方法参数解析器,@MatrixVariable使用的是MatrixVariableMapMethodArgumentResolver和MatrixVariableMethodArgumentResolver,接下来一起来深入了解一下其源码实现。  类解析  HandlerMethodArgumentResolver和AbstractNamedValueMethodArgumentResolve...

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

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

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

  类解析

  HandlerMethodArgumentResolverAbstractNamedValueMethodArgumentResolver是解析策略的上层定义和抽象,关于这两个类可以参照《Spring 注解面面通 之 @CookieValue参数绑定源码解析》中的解析。

  MatrixVariableMapMethodArgumentResolverMatrixVariableMethodArgumentResolver则是用来针对不用类型的方法参数的解析。

  1) MatrixVariableMapMethodArgumentResolver实现了HandlerMethodArgumentResolversupportsParameter(...)resolveArgument(...)方法。

  MatrixVariableMapMethodArgumentResolver在某些条件成立的情况下才会使用此类进行解析:

  ① 方法参数由@MatrixVariable注解注释。

  ② 方法参数类型必须是Map类型。

  ③ 注释方法参数的@MatrixVariablename属性不能有值。

  resolveArgument(...)解析参数从NativeWebRequest.getAttribute( HandlerMapping.MATRIX_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST)获得。

package org.springframework.web.servlet.mvc.method.annotation;

import java.util.Collections;
import java.util.List;
import java.util.Map;

import org.springframework.core.MethodParameter;
import org.springframework.core.ResolvableType;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.MatrixVariable;
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.RequestAttributes;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.HandlerMapping;

/**
 * 解析带@MatrixVariable注解的Map类型的参数,其中注解未指定名称. 
 * 换句话说,这个解析器的目的是提供对多个矩阵变量的访问,这些变量可以是全部的,也可以是与特定路径变量相关联的.
 *
 * 指定名称时,类型为Map的参数被视为具有映射值的单个属性,并由MatrixVariableMethodArgumentResolver解析.
 */
public class MatrixVariableMapMethodArgumentResolver implements HandlerMethodArgumentResolver {

	/**
	 * 方法参数检查.
	 * 方法参数需包含@MatrixVariable注解、为Map类型参数,且@MatrixVariable的name属性不可有值.
	 */
	@Override
	public boolean supportsParameter(MethodParameter parameter) {
		MatrixVariable matrixVariable = parameter.getParameterAnnotation(MatrixVariable.class);
		return (matrixVariable != null && Map.class.isAssignableFrom(parameter.getParameterType()) &&
				!StringUtils.hasText(matrixVariable.name()));
	}

	/**
	 * 解析方法参数值.
	 */
	@Override
	@Nullable
	public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
			NativeWebRequest request, @Nullable WebDataBinderFactory binderFactory) throws Exception {
		// 获取NativeWebRequest中存储的HandlerMapping.MATRIX_VARIABLES_ATTRIBUTE的值.
		@SuppressWarnings("unchecked")
		Map<String, MultiValueMap<String, String>> matrixVariables =
				(Map<String, MultiValueMap<String, String>>) request.getAttribute(
						HandlerMapping.MATRIX_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
		// 矩阵变量为空.
		if (CollectionUtils.isEmpty(matrixVariables)) {
			return Collections.emptyMap();
		}
		
		MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
		MatrixVariable ann = parameter.getParameterAnnotation(MatrixVariable.class);
		Assert.state(ann != null, "No MatrixVariable annotation");
		// 获取@MatrixVariable指定的矩阵变量的路径值.
		String pathVariable = ann.pathVar();
		// @MatrixVariable指定的矩阵变量的路径值不为空.
		if (!pathVariable.equals(ValueConstants.DEFAULT_NONE)) {
			// 根据路径获取矩阵变量.
			MultiValueMap<String, String> mapForPathVariable = matrixVariables.get(pathVariable);
			if (mapForPathVariable == null) {
				return Collections.emptyMap();
			}
			map.putAll(mapForPathVariable);
		}
		// @MatrixVariable指定的矩阵变量的路径值为空.
		else {
			// 设置全部矩阵变量.
			for (MultiValueMap<String, String> vars : matrixVariables.values()) {
				vars.forEach((name, values) -> {
					for (String value : values) {
						map.add(name, value);
					}
				});
			}
		}
		// MultiValueMap转换为Map.
		return (isSingleValueMap(parameter) ? map.toSingleValueMap() : map);
	}
	
	/**
	 * 方法参数类型是否是MultiValueMap类型.
	 * @param parameter 方法参数.
	 * @return 是否是MultiValueMap类型.
	 */
	private boolean isSingleValueMap(MethodParameter parameter) {
		if (!MultiValueMap.class.isAssignableFrom(parameter.getParameterType())) {
			ResolvableType[] genericTypes = ResolvableType.forMethodParameter(parameter).getGenerics();
			if (genericTypes.length == 2) {
				Class<?> declaredClass = genericTypes[1].getRawClass();
				return (declaredClass == null || !List.class.isAssignableFrom(declaredClass));
			}
		}
		return false;
	}

}

  2) MatrixVariableMethodArgumentResolver继承自抽象AbstractNamedValueMethodArgumentResolver(可以参照《Spring 注解面面通 之 @CookieValue参数绑定源码解析》)。

  MatrixVariableMethodArgumentResolver相对MatrixVariableMapMethodArgumentResolver来说,实现基本相同,其在某些条件成立的情况下才会使用此类进行解析:

  ① 方法参数由@MatrixVariable注解注释。

  ② 当方法参数类型是Map时,要求其必须由@MatrixVariable注解注释,同时@MatrixVariablename属性必须有值。

  resolveArgument(...)解析参数从NativeWebRequest.getAttribute( HandlerMapping.MATRIX_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST)获得。

package org.springframework.web.servlet.mvc.method.annotation;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.springframework.core.MethodParameter;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.annotation.MatrixVariable;
import org.springframework.web.bind.annotation.ValueConstants;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.method.annotation.AbstractNamedValueMethodArgumentResolver;
import org.springframework.web.servlet.HandlerMapping;

/**
 * 解析用@MatrixVariable注释的参数.
 *
 * 如果方法参数是Map类型,它将由MatrixVariableMapMethodArgumentResolver解析,除非注释指定了一个名称,
 * 	在这种情况下,它被视为Map类型的单个属性(而不是在映射中收集的多个属性).
 */
public class MatrixVariableMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver {
	
	/**
	 * Constructor.
	 */
	public MatrixVariableMethodArgumentResolver() {
		super(null);
	}

	/**
	 * 方法参数检查.
	 * 方法参数需包含@MatrixVariable注解.
	 * 若方法参数为Map类型参数,且@MatrixVariable的name属性必须有值.
	 */
	@Override
	public boolean supportsParameter(MethodParameter parameter) {
		if (!parameter.hasParameterAnnotation(MatrixVariable.class)) {
			return false;
		}
		if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
			MatrixVariable matrixVariable = parameter.getParameterAnnotation(MatrixVariable.class);
			return (matrixVariable != null && StringUtils.hasText(matrixVariable.name()));
		}
		return true;
	}

	/**
	 * 创建NamedValueInfo.
	 */
	@Override
	protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
		MatrixVariable ann = parameter.getParameterAnnotation(MatrixVariable.class);
		Assert.state(ann != null, "No MatrixVariable annotation");
		return new MatrixVariableNamedValueInfo(ann);
	}

	/**
	 * 解析方法参数值.
	 */
	@Override
	@SuppressWarnings("unchecked")
	@Nullable
	protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
		// 获取NativeWebRequest中存储的HandlerMapping.MATRIX_VARIABLES_ATTRIBUTE的值.
		Map<String, MultiValueMap<String, String>> pathParameters = (Map<String, MultiValueMap<String, String>>)
				request.getAttribute(HandlerMapping.MATRIX_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
		// 矩阵变量为空.
		if (CollectionUtils.isEmpty(pathParameters)) {
			return null;
		}

		MatrixVariable ann = parameter.getParameterAnnotation(MatrixVariable.class);
		Assert.state(ann != null, "No MatrixVariable annotation");
		// 获取@MatrixVariable指定的矩阵变量的路径值.
		String pathVar = ann.pathVar();
		List<String> paramValues = null;
		// @MatrixVariable指定的矩阵变量的路径值不为空.
		if (!pathVar.equals(ValueConstants.DEFAULT_NONE)) {
			if (pathParameters.containsKey(pathVar)) {
				paramValues = pathParameters.get(pathVar).get(name);
			}
		}
		// @MatrixVariable指定的矩阵变量的路径值为空.
		else {
			boolean found = false;
			paramValues = new ArrayList<>();
			// 设置全部矩阵变量.
			for (MultiValueMap<String, String> params : pathParameters.values()) {
				if (params.containsKey(name)) {
					if (found) {
						String paramType = parameter.getNestedParameterType().getName();
						throw new ServletRequestBindingException(
								"Found more than one match for URI path parameter '" + name +
								"' for parameter type [" + paramType + "]. Use 'pathVar' attribute to disambiguate.");
					}
					paramValues.addAll(params.get(name));
					found = true;
				}
			}
		}
		
		if (CollectionUtils.isEmpty(paramValues)) {
			return null;
		}
		else if (paramValues.size() == 1) {
			return paramValues.get(0);
		}
		else {
			return paramValues;
		}
	}

	/**
	 * 处理参数值缺失异常.
	 */
	@Override
	protected void handleMissingValue(String name, MethodParameter parameter) throws ServletRequestBindingException {
		throw new ServletRequestBindingException("Missing matrix variable '" + name +
				"' for method parameter of type " + parameter.getNestedParameterType().getSimpleName());
	}


	/**
	 * MatrixVariableNamedValueInfo.
	 */
	private static class MatrixVariableNamedValueInfo extends NamedValueInfo {

		private MatrixVariableNamedValueInfo(MatrixVariable annotation) {
			super(annotation.name(), annotation.required(), annotation.defaultValue());
		}
	}

}

  3) NativeWebRequest
HandlerMapping.MATRIX_VARIABLES_ATTRIBUTE属性

  分析过MatrixVariableMapMethodArgumentResolverMatrixVariableMethodArgumentResolver源码后,发现矩阵变量都是从NativeWebRequest属性中获得,那么是在哪里进行的矩阵模板变量解析呢?

  ① 涉及矩阵解析的调用过程。

DispatcherServlet.doDispatch(...) --调用--> 
DispatcherServlet.getHandler(...) --调用--> 
AbstractHandlerMapping.getHandler(...) --调用--> 
AbstractHandlerMethodMapping.getHandlerInternal(...) --调用--> 
AbstractHandlerMethodMapping.lookupHandlerMethod(...) --调用--> 
RequestMappingInfoHandlerMapping.handleMatch(...) --调用-->
RequestMappingInfoHandlerMapping.extractMatrixVariables(...) --调用-->
根据已解析的URI模板变量,来解析矩阵变量

  ② 查看RequestMappingInfoHandlerMapping.handleMatch(...)方法,可以发现,在这里对矩阵变量进行了解析,并设置到NativeWebRequest属性中。

/**
 * 暴露URI模板变量、矩阵变量和可生成的媒体类型.
 */
@Override
protected void handleMatch(RequestMappingInfo info, String lookupPath, HttpServletRequest request) {
    super.handleMatch(info, lookupPath, request);

    String bestPattern;
    Map<String, String> uriVariables;
    Map<String, String> decodedUriVariables;

    Set<String> patterns = info.getPatternsCondition().getPatterns();
    // 若不存在匹配模式,则均设置为空.
    if (patterns.isEmpty()) {
        bestPattern = lookupPath;
        uriVariables = Collections.emptyMap();
        decodedUriVariables = Collections.emptyMap();
    }
    else {
        bestPattern = patterns.iterator().next();
        // 解析URI模板变量.
        uriVariables = getPathMatcher().extractUriTemplateVariables(bestPattern, lookupPath);
        // 对URI模板变量进行解码.
        decodedUriVariables = getUrlPathHelper().decodePathVariables(request, uriVariables);
    }
    // 设置匹配模式到HttpServletRequest的BEST_MATCHING_PATTERN_ATTRIBUTE.
    request.setAttribute(BEST_MATCHING_PATTERN_ATTRIBUTE, bestPattern);
    // 设置URI模板变量到HttpServletRequest的HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE.
    request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, decodedUriVariables);
    // 处理矩阵变量.
    if (isMatrixVariableContentAvailable()) {
        // 提取矩阵变量.
        Map<String, MultiValueMap<String, String>> matrixVars = extractMatrixVariables(request, uriVariables);
        // 设置矩阵变量到HttpServletRequest的HandlerMapping.MATRIX_VARIABLES_ATTRIBUTE.
        request.setAttribute(HandlerMapping.MATRIX_VARIABLES_ATTRIBUTE, matrixVars);
    }
    // 处理可生成的媒体类型.
    if (!info.getProducesCondition().getProducibleMediaTypes().isEmpty()) {
        // 获取可生成的媒体类型.
        Set<MediaType> mediaTypes = info.getProducesCondition().getProducibleMediaTypes();
        // 设置可生成媒体类型到HttpServletRequest的PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE.
        request.setAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE, mediaTypes);
    }
}

  ③ RequestMappingInfoHandlerMapping.extractMatrixVariables(...)负责矩阵变量的具体解析。

/**
 * 提取矩阵变量.
 * @param request 当前请求实例.
 * @param uriVariables URI模板变量.
 * @return 矩阵变量.
 */
private Map<String, MultiValueMap<String, String>> extractMatrixVariables(
    HttpServletRequest request, Map<String, String> uriVariables) {

    Map<String, MultiValueMap<String, String>> result = new LinkedHashMap<>();
    // 遍历矩阵变量.
    uriVariables.forEach((uriVarKey, uriVarValue) -> {
        // URI模板变量值中存在=.
        int equalsIndex = uriVarValue.indexOf('=');
        if (equalsIndex == -1) {
            return;
        }

        String matrixVariables;
        // URI模板变量值中存在;,若不存在;或;在首位,或=在;前,URI模板变量的值即是矩阵变量.
        int semicolonIndex = uriVarValue.indexOf(';');
        if ((semicolonIndex == -1) || (semicolonIndex == 0) || (equalsIndex < semicolonIndex)) {
            matrixVariables = uriVarValue;
        }
        else {
            matrixVariables = uriVarValue.substring(semicolonIndex + 1);
            uriVariables.put(uriVarKey, uriVarValue.substring(0, semicolonIndex));
        }
        // 解析矩阵变量到Map.
        MultiValueMap<String, String> vars = WebUtils.parseMatrixVariables(matrixVariables);
        result.put(uriVarKey, getUrlPathHelper().decodeMatrixVariables(request, vars));
    });
    return result;
}

  总结

  以上便是@MatrixVariable对方法参数解析的全过程,对于理解@MatrixVariable原理来说还是十分有用处的,更有利于在实际开发中解决那些疑难杂症。

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

本文地址:https://blog.csdn.net/securitit/article/details/110676001