17、SpringMVC源码分析 - @MatrixVariable的使用及原理

前言

@MatrixVariable可以从url中获取路径参数。

一、使用

单个参数

接口方法:

	@ApiOperation(value = "测试@MatrixVariable", notes = "单个参数")
    @GetMapping(value = "/matrix/{param}", produces = {
   
     MediaType.APPLICATION_JSON_VALUE})
    public ResponseEntity aaa(@PathVariable String param, @MatrixVariable String var) throws BaseException {
   
     
        ResponseEntity responseEntity = new ResponseEntity();
        System.out.println("param: " + param);
        System.out.println("var: " + var);
        return responseEntity;
    }

请求路径:

matrix/param1;var=matrixVar

结果:

param: param1
var: matrixVar

多个参数

接口方法:

	@ApiOperation(value = "测试@MatrixVariable", notes = "多个参数")
    @GetMapping(value = "/matrix/{param}/{param2}", produces = {
   
     MediaType.APPLICATION_JSON_VALUE})
    public ResponseEntity aaa(@PathVariable String param, @MatrixVariable(pathVar = "param2") String var) throws BaseException {
   
     
        ResponseEntity responseEntity = new ResponseEntity();
        System.out.println("param: " + param);
        System.out.println("var: " + var);
        return responseEntity;
    }

请求路径:

matrix/param1;var=matrixVar;var1=matrixVar/param2;var=matrixVar2

结果:

param: param1
var: matrixVar2

Map类型参数

接口方法:

	@ApiOperation(value = "测试@MatrixVariable", notes = "多个参数")
    @GetMapping(value = "/matrix/{param}/{param2}", produces = {
   
     MediaType.APPLICATION_JSON_VALUE})
    public ResponseEntity aaa(@PathVariable String param, @MatrixVariable(pathVar = "param1") Map var) throws BaseException {
   
     
        ResponseEntity responseEntity = new ResponseEntity();
        System.out.println("param: " + param);
        System.out.println("var: " + var);
        return responseEntity;
    }

请求路径:

matrix/param1;var=matrixVar;var1=matrixVar/param2;var=matrixVar2

结果:

param: param1
var: {
   
     var=matrixVar1, var1=matrixVar1}

二、原理

1 解析路径参数

在获取处理器的过程中,RequestMappingHandlerMapping调用getHandler方法,找到匹配当前请求的HandlerMethod,然后处理匹配到的HandlerMapping,进而解析路径的参数

1、handleMatch( )

RequestMappingInfoHandlerMapping.java

protected void handleMatch(RequestMappingInfo info, String lookupPath, HttpServletRequest request) {
   
     
		super.handleMatch(info, lookupPath, request);

		RequestCondition<?> condition = info.getActivePatternsCondition();
		if (condition instanceof PathPatternsRequestCondition) {
   
     
			extractMatchDetails((PathPatternsRequestCondition) condition, lookupPath, request);
		}
		else {
   
     
			//提取路径里的详细信息
			extractMatchDetails((PatternsRequestCondition) condition, lookupPath, request);
		}

		if (!info.getProducesCondition().getProducibleMediaTypes().isEmpty()) {
   
     
			Set<MediaType> mediaTypes = info.getProducesCondition().getProducibleMediaTypes();
			request.setAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE, mediaTypes);
		}
	}

2、extractMatchDetails( )

private void extractMatchDetails(
			PatternsRequestCondition condition, String lookupPath, HttpServletRequest request) {
   
     

		String bestPattern;
		Map<String, String> uriVariables;
		if (condition.isEmptyPathMapping()) {
   
     
			bestPattern = lookupPath;
			uriVariables = Collections.emptyMap();
		}
		else {
   
     
			bestPattern = condition.getPatterns().iterator().next();
			//提取出UriTemplateVariables参数值,供@PathVariable使用
			uriVariables = getPathMatcher().extractUriTemplateVariables(bestPattern, lookupPath);
			if (!getUrlPathHelper().shouldRemoveSemicolonContent()) {
   
     
				//提取出路径里UriTemplateVariables变量后的参数,设置到request属性中,供@MatrixVariable使用
				request.setAttribute(MATRIX_VARIABLES_ATTRIBUTE, extractMatrixVariables(request, uriVariables));
			}
			uriVariables = getUrlPathHelper().decodePathVariables(request, uriVariables);
		}
		request.setAttribute(BEST_MATCHING_PATTERN_ATTRIBUTE, bestPattern);
		//将uriVariables设置到request属性中
		request.setAttribute(URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriVariables);
	}

2 MatrixVariableMethodArgumentResolver使用参数值

在MatrixVariableMethodArgumentResolver中解析@MatrixVariable注解的参数值,对应上述单个参数和多个参数的示例。

参数值的结果如图:

 

protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
   
     
		//从request中获取pathParameters,结构如上图
		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;

		if (!pathVar.equals(ValueConstants.DEFAULT_NONE)) {
   
     
			if (pathParameters.containsKey(pathVar)) {
   
     
				//从pathParameters获取路径参数对应Map,再从map中获取MatrixVariable对应值
				paramValues = pathParameters.get(pathVar).get(name);
			}
		}
		else {
   
     
			boolean found = false;
			paramValues = new ArrayList<>();
			//遍历pathParameters,根据MatrixVariable名称取匹配
			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;
		}
	}

3 MatrixVariableMapMethodArgumentResolver使用参数值

解析@MatrixVariable注解的参数值,对应上述Map类型参数的示例。

public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
			NativeWebRequest request, @Nullable WebDataBinderFactory binderFactory) throws Exception {
   
     

		@SuppressWarnings("unchecked")
		//从request中获取pathParameters
		Map<String, MultiValueMap<String, String>> matrixVariables =
				(Map<String, MultiValueMap<String, String>>) request.getAttribute(
						HandlerMapping.MATRIX_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);

		if (CollectionUtils.isEmpty(matrixVariables)) {
   
     
			//不存在时,返回空的map
			return Collections.emptyMap();
		}

		MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
		MatrixVariable ann = parameter.getParameterAnnotation(MatrixVariable.class);
		Assert.state(ann != null, "No MatrixVariable annotation");
		String pathVariable = ann.pathVar();

		if (!pathVariable.equals(ValueConstants.DEFAULT_NONE)) {
   
     
			//根据路径参数获取map
			MultiValueMap<String, String> mapForPathVariable = matrixVariables.get(pathVariable);
			if (mapForPathVariable == null) {
   
     
				return Collections.emptyMap();
			}
			map.putAll(mapForPathVariable);
		}
		else {
   
     
			//遍历所有路径参数的matrixVariables
			for (MultiValueMap<String, String> vars : matrixVariables.values()) {
   
     
				vars.forEach((name, values) -> {
   
     
					for (String value : values) {
   
     
						map.add(name, value);
					}
				});
			}
		}

		return (isSingleValueMap(parameter) ? map.toSingleValueMap() : map);
	}

三、配置

在解析MatrixVariable变量的之前,有一个判断,检验UrlPathHelper中的removeSemicolonContent参数,removeSemicolonContent默认是true,默认不会提取MatrixVariable变量,因此需要设置为false,才能进入该分支。

	if (!getUrlPathHelper().shouldRemoveSemicolonContent()) {
   
     
		  request.setAttribute(MATRIX_VARIABLES_ATTRIBUTE, extractMatrixVariables(request, uriVariables));
	}

配置:

@Configuration
public class ConfigurePathMatchConfig implements WebMvcConfigurer {
   
     
    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
   
     
        UrlPathHelper urlPathHelper = new UrlPathHelper();
        //这里设置为false
        urlPathHelper.setRemoveSemicolonContent(false);
        configurer.setUrlPathHelper(urlPathHelper);
    }
}

这样,在RequestMappingHandlerMapping创建过程中,会回调configurePathMatch( )方法。

总结

本文简单介绍了@MatrixVariable注解的使用及原理。