网站开发个人简介范文/域名解析ip
Spring MVC中使用HandlerMethodArgumentResolver
策略接口来定义处理器方法参数解析器,@PathVariable
使用的是PathVariableMapMethodArgumentResolver
和PathVariableMethodArgumentResolver
,接下来一起来深入了解一下其源码实现。
类结构
类解析
HandlerMethodArgumentResolver
和AbstractNamedValueMethodArgumentResolver
是解析策略的上层定义和抽象,关于这两个类可以参照《Spring 注解面面通 之 @CookieValue参数绑定源码解析》中的解析。
PathVariableMapMethodArgumentResolver
和PathVariableMethodArgumentResolver
则是用来针对不用类型的方法参数的解析。
1) PathVariableMapMethodArgumentResolver
实现了HandlerMethodArgumentResolver
的supportsParameter(...)
和resolveArgument(...)
方法。
PathVariableMapMethodArgumentResolver
相对比较简单,但在某些条件成立的情况下才会使用此类进行解析:
① 方法参数由@PathVariable
注解注释。
② 方法参数类型必须是Map
类型。
③ 注释方法参数的@PathVariable
的value
属性不能有值。
resolveArgument(...)
解析参数从NativeWebRequest.getAttribute( HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST)
获得。
package org.springframework.web.servlet.mvc.method.annotation;import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;import org.springframework.core.MethodParameter;
import org.springframework.lang.Nullable;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.PathVariable;
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;/*** 解析用@PathVariable注释的Map方法参数,其中注解不能指定路径变量名. * 创建的Map包含所有URI模板名称/值对.*/
public class PathVariableMapMethodArgumentResolver implements HandlerMethodArgumentResolver {/*** 检查方法参数,方法参数需包含@PathVariable注解、为Map类型参数,且@PathVariable的value属性不可有值.*/@Overridepublic boolean supportsParameter(MethodParameter parameter) {PathVariable ann = parameter.getParameterAnnotation(PathVariable.class);return (ann != null && Map.class.isAssignableFrom(parameter.getParameterType()) &&!StringUtils.hasText(ann.value()));}/*** 返回包含所有URI模板变量的映射或空映射.*/@Overridepublic Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {// 获取NativeWebRequest中存储的HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE的值.@SuppressWarnings("unchecked")Map<String, String> uriTemplateVars =(Map<String, String>) webRequest.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);if (!CollectionUtils.isEmpty(uriTemplateVars)) {return new LinkedHashMap<>(uriTemplateVars);}else {return Collections.emptyMap();}}}
2) PathVariableMethodArgumentResolver
继承自抽象AbstractNamedValueMethodArgumentResolver
(可以参照《Spring 注解面面通 之 @CookieValue参数绑定源码解析》),同时实现了UriComponentsContributor
(主要用来参与URI
的编解码)。
PathVariableMethodArgumentResolver
相对PathVariableMapMethodArgumentResolver
来说,类内容较多,但关注主要实现,也是很清晰的,其在某些条件成立的情况下才会使用此类进行解析:
① 方法参数由@PathVariable
注解注释。
② 当方法参数类型是Map
时,要求其必须由@PathVariable
注解注释,同时@PathVariable
的value
属性必须有值。
resolveArgument(...)
解析参数从NativeWebRequest.getAttribute( HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST)
获得。
package org.springframework.web.servlet.mvc.method.annotation;import java.util.HashMap;
import java.util.Map;import org.springframework.core.MethodParameter;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.MissingPathVariableException;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.annotation.PathVariable;
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.method.support.ModelAndViewContainer;
import org.springframework.web.method.support.UriComponentsContributor;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.View;
import org.springframework.web.util.UriComponentsBuilder;/*** 解析用@PathVariable注释的方法参数.** @PathVariable 用于从URI模板变量解析的命名值.* 它始终是必需的,并且没有可依赖的默认值.** 如果方法参数类型是Map,则注释中指定的名称将用于解析URI变量字符串值.* 如果注册了Converter或PropertyEditor,则通过类型转换将该值转换为Map.** 调用WebDataBinder将类型转换应用于尚未与方法参数类型匹配的已解析路径变量值.*/
public class PathVariableMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolverimplements UriComponentsContributor {/*** String类型描述符.*/private static final TypeDescriptor STRING_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(String.class);/*** 检查方法参数.* 方法参数需包含@PathVariable.* 若参数类型为Map,则@PathVariable的value属性必须有值.*/@Overridepublic boolean supportsParameter(MethodParameter parameter) {// 方法参数是否含有@PathVariable注解.if (!parameter.hasParameterAnnotation(PathVariable.class)) {return false;}// 方法参数类型是Map、且含有@PathVariable注解、@PathVariable注解的value属性必须有值.if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {PathVariable pathVariable = parameter.getParameterAnnotation(PathVariable.class);return (pathVariable != null && StringUtils.hasText(pathVariable.value()));}return true;}/*** 创建NamedValueInfo.*/@Overrideprotected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {PathVariable ann = parameter.getParameterAnnotation(PathVariable.class);Assert.state(ann != null, "No PathVariable annotation");return new PathVariableNamedValueInfo(ann);}/*** 解析参数值.*/@Override@SuppressWarnings("unchecked")@Nullableprotected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {// 获取NativeWebRequest中存储的HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE的值.Map<String, String> uriTemplateVars = (Map<String, String>) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);return (uriTemplateVars != null ? uriTemplateVars.get(name) : null);}/*** 处理缺失参数异常.*/@Overrideprotected void handleMissingValue(String name, MethodParameter parameter) throws ServletRequestBindingException {throw new MissingPathVariableException(name, parameter);}/*** 处理已解析的参数值.*/@Override@SuppressWarnings("unchecked")protected void handleResolvedValue(@Nullable Object arg, String name, MethodParameter parameter,@Nullable ModelAndViewContainer mavContainer, NativeWebRequest request) {String key = View.PATH_VARIABLES;int scope = RequestAttributes.SCOPE_REQUEST;Map<String, Object> pathVars = (Map<String, Object>) request.getAttribute(key, scope);if (pathVars == null) {pathVars = new HashMap<>();request.setAttribute(key, pathVars, scope);}pathVars.put(name, arg);}/*** 处理方法参数.*/@Overridepublic void contributeMethodArgument(MethodParameter parameter, Object value,UriComponentsBuilder builder, Map<String, Object> uriVariables, ConversionService conversionService) {if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {return;}PathVariable ann = parameter.getParameterAnnotation(PathVariable.class);String name = (ann != null && !StringUtils.isEmpty(ann.value()) ? ann.value() : parameter.getParameterName());String formatted = formatUriValue(conversionService, new TypeDescriptor(parameter.nestedIfOptional()), value);uriVariables.put(name, formatted);}/*** 格式化URI模板变量值.* @param cs 类型转换服务.* @param sourceType 源类型描述符.* @param value 待转换的值.* @return 转换后的值.*/@Nullableprotected String formatUriValue(@Nullable ConversionService cs, @Nullable TypeDescriptor sourceType, Object value) {if (value instanceof String) {return (String) value;}else if (cs != null) {return (String) cs.convert(value, sourceType, STRING_TYPE_DESCRIPTOR);}else {return value.toString();}}/*** PathVariableNamedValueInfo.*/private static class PathVariableNamedValueInfo extends NamedValueInfo {public PathVariableNamedValueInfo(PathVariable annotation) {super(annotation.name(), annotation.required(), ValueConstants.DEFAULT_NONE);}}}
3) NativeWebRequest
的
HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE
属性
分析过PathVariableMapMethodArgumentResolver
和PathVariableMethodArgumentResolver
源码后,发现URI
模板变量都是从NativeWebRequest
属性中获得,那么是在哪里进行的URI
模板变量解析呢?
① 涉及URI
模板变量解析的调用过程。
DispatcherServlet.doDispatch(...) --调用-->
DispatcherServlet.getHandler(...) --调用-->
AbstractHandlerMapping.getHandler(...) --调用-->
AbstractHandlerMethodMapping.getHandlerInternal(...) --调用-->
AbstractHandlerMethodMapping.lookupHandlerMethod(...) --调用-->
RequestMappingInfoHandlerMapping.handleMatch(...) --调用-->
解析URI模板变量,并设置到NativeWebRequest属性中
② 查看RequestMappingInfoHandlerMapping.handleMatch(...)
方法,可以发现,在这里对URI
模板变量进行了解析,并设置到NativeWebRequest
属性中。除了对URI
模板变量处理外,还对矩阵变量、可生产媒体类型进行了处理,同样设置到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);}
}
总结
以上便是@PathVariable
对方法参数解析的全过程,对于理解@PathVariable
原理来说还是十分有用处的,更有利于在实际开发中解决那些疑难杂症。
若文中存在错误和不足,欢迎指正!