Spring MVC请求处理(三) - HandlerMapping(二)

本文分析RequestMappingInfoHandlerMapping类和RequestMappingHandlerMapping类。

RequestMappingInfoHandlerMapping类

RequestMappingInfoHandlerMapping类继承了AbstractHandlerMethodMapping类,利用RequestMappingInfo类充当了映射的角色。

public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMethodMapping {

    protected RequestMappingInfoHandlerMapping() {
        setHandlerMethodMappingNamingStrategy(new RequestMappingInfoHandlerMethodMappingNamingStrategy());
    }

    @Override
    protected Set getMappingPathPatterns(RequestMappingInfo info) {
        return info.getPatternsCondition().getPatterns();
    }

    @Override
    protected RequestMappingInfo getMatchingMapping(RequestMappingInfo info, HttpServletRequest request) {
        return info.getMatchingCondition(request);
    }

    @Override
    protected Comparator getMappingComparator(final HttpServletRequest request) {
        return new Comparator() {
            @Override
            public int compare(RequestMappingInfo info1, RequestMappingInfo info2) {
                return info1.compareTo(info2, request);
            }
        };
    }

    // 省略一些代码
}

有匹配

上一篇文章提到AbstractHandlerMethodMapping类的handleMatch相当于回调函数,表示有匹配的处理器时的执行动作,RequestMappingInfoHandlerMapping类重写了handleMatch方法。

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

    String bestPattern;
    Map uriVariables;
    Map decodedUriVariables;

    Set patterns = info.getPatternsCondition().getPatterns();
    if (patterns.isEmpty()) {
        bestPattern = lookupPath;
        uriVariables = Collections.emptyMap();
        decodedUriVariables = Collections.emptyMap();
    }
    else {
        bestPattern = patterns.iterator().next();
        uriVariables = getPathMatcher().extractUriTemplateVariables(bestPattern, lookupPath);
        decodedUriVariables = getUrlPathHelper().decodePathVariables(request, uriVariables);
    }

    request.setAttribute(BEST_MATCHING_PATTERN_ATTRIBUTE, bestPattern);
    request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, decodedUriVariables);

    if (isMatrixVariableContentAvailable()) {
        Map> matrixVars = extractMatrixVariables(request, uriVariables);
        request.setAttribute(HandlerMapping.MATRIX_VARIABLES_ATTRIBUTE, matrixVars);
    }

    if (!info.getProducesCondition().getProducibleMediaTypes().isEmpty()) {
        Set mediaTypes = info.getProducesCondition().getProducibleMediaTypes();
        request.setAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE, mediaTypes);
    }
}
  • 调用父类AbstractHandlerMethodMapping类的handleMatch方法;
  • 获得匹配映射的URL模式,根据请求路径提取并解码URI模板参数(即路径模式中{}表示的参数),将解码后的模板参数填充到请求中传递下去;
  • 提取并解码URL矩阵参数,将解码后的矩阵参数填充到请求中传递下去。

无匹配

上一篇文章提到AbstractHandlerMethodMapping类的handleNoMatch相当于回调函数,表示无匹配的处理器时的执行动作,RequestMappingInfoHandlerMapping类重写了handleNoMatch方法,针对不匹配的原因分别抛出不同的异常。

@Override
protected HandlerMethod handleNoMatch(
        Set infos, String lookupPath, HttpServletRequest request) throws ServletException {

    PartialMatchHelper helper = new PartialMatchHelper(infos, request);
    if (helper.isEmpty()) {
        return null;
    }

    if (helper.hasMethodsMismatch()) {
        Set methods = helper.getAllowedMethods();
        if (HttpMethod.OPTIONS.matches(request.getMethod())) {
            HttpOptionsHandler handler = new HttpOptionsHandler(methods);
            return new HandlerMethod(handler, HTTP_OPTIONS_HANDLE_METHOD);
        }
        throw new HttpRequestMethodNotSupportedException(request.getMethod(), methods);
    }

    if (helper.hasConsumesMismatch()) {
        Set mediaTypes = helper.getConsumableMediaTypes();
        MediaType contentType = null;
        if (StringUtils.hasLength(request.getContentType())) {
            try {
                contentType = MediaType.parseMediaType(request.getContentType());
            }
            catch (InvalidMediaTypeException ex) {
                throw new HttpMediaTypeNotSupportedException(ex.getMessage());
            }
        }
        throw new HttpMediaTypeNotSupportedException(contentType, new ArrayList(mediaTypes));
    }

    if (helper.hasProducesMismatch()) {
        Set mediaTypes = helper.getProducibleMediaTypes();
        throw new HttpMediaTypeNotAcceptableException(new ArrayList(mediaTypes));
    }

    if (helper.hasParamsMismatch()) {
        List conditions = helper.getParamConditions();
        throw new UnsatisfiedServletRequestParameterException(conditions, request.getParameterMap());
    }

    return null;
}

RequestMappingHandlerMapping类

经过层层分析终于来到了RequestMappingHandlerMapping类,它继承了RequestMappingInfoHandlerMapping抽象类,重写了一些与映射有关的方法。

成员变量

RequestMappingHandlerMapping类的成员变量都与自己有关,其代码如下所示:

public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMapping
        implements MatchableHandlerMapping, EmbeddedValueResolverAware {

    private boolean useSuffixPatternMatch = true;

    private boolean useRegisteredSuffixPatternMatch = false;

    private boolean useTrailingSlashMatch = true;

    private ContentNegotiationManager contentNegotiationManager = new ContentNegotiationManager();

    private StringValueResolver embeddedValueResolver;

    private RequestMappingInfo.BuilderConfiguration config = new RequestMappingInfo.BuilderConfiguration();


    public void setUseSuffixPatternMatch(boolean useSuffixPatternMatch) {
        this.useSuffixPatternMatch = useSuffixPatternMatch;
    }

    public void setUseRegisteredSuffixPatternMatch(boolean useRegisteredSuffixPatternMatch) {
        this.useRegisteredSuffixPatternMatch = useRegisteredSuffixPatternMatch;
        this.useSuffixPatternMatch = (useRegisteredSuffixPatternMatch || this.useSuffixPatternMatch);
    }

    public void setUseTrailingSlashMatch(boolean useTrailingSlashMatch) {
        this.useTrailingSlashMatch = useTrailingSlashMatch;
    }

    public void setContentNegotiationManager(ContentNegotiationManager contentNegotiationManager) {
        Assert.notNull(contentNegotiationManager, "ContentNegotiationManager must not be null");
        this.contentNegotiationManager = contentNegotiationManager;
    }

    @Override
    public void setEmbeddedValueResolver(StringValueResolver resolver) {
        this.embeddedValueResolver = resolver;
    }

    // 省略一些代码
}
  • useSuffixPatternMatch表示是否启用后缀模式匹配,默认启用,若启用那么映射到/users的方法也匹配/users.*(这里的点号就是实际的点号,不是正则表达式的元字符,星号才表示任意匹配,Ant-Style模式);
  • useRegisteredSuffixPatternMatch表示是否启用注册后缀模式匹配,默认禁用,若启用那么后缀模式匹配只针对显式注册到内容协商管理器的路径扩展名。启用useRegisteredSuffixPatternMatch会启用useSuffixPatternMatch;
  • useTrailingSlashMatch表示是否启用末尾斜线匹配,默认启用,若启用那么映射到/users的方法也匹配/users/;
  • 上述三个属性可以继承WebMvcConfigurerAdapter类并重写configurePathMatch方法进行显式配置,如:
    @Configuration
    public class WebMvcConfigurer extends WebMvcConfigurerAdapter {
    
        @Override
        public void configurePathMatch(PathMatchConfigurer configurer) {
            configurer.setUseSuffixPatternMatch(true);
            configurer.setUseRegisteredSuffixPatternMatch(true);
            configurer.setUseTrailingSlashMatch(true);
        }
    }
    

初始化

RequestMappingHandlerMapping类重写了AbstractHandlerMethodMapping类的afterPropertiesSet方法,先将各个属性保存到配置,然后调用AbstractHandlerMethodMapping类的afterPropertiesSet发现被@RequestMapping注解修饰的方法。

@Override
public void afterPropertiesSet() {
    this.config = new RequestMappingInfo.BuilderConfiguration();
    this.config.setUrlPathHelper(getUrlPathHelper());
    this.config.setPathMatcher(getPathMatcher());
    this.config.setSuffixPatternMatch(this.useSuffixPatternMatch);
    this.config.setTrailingSlashMatch(this.useTrailingSlashMatch);
    this.config.setRegisteredSuffixPatternMatch(this.useRegisteredSuffixPatternMatch);
    this.config.setContentNegotiationManager(getContentNegotiationManager());

    super.afterPropertiesSet();
}

AbstractHandlerMethodMapping类的isHandler抽象方法用于在发现HandlerMethod的过程中判断bean是否需要被扫描以发现其中的HandlerMethod,RequestMappingHandlerMapping类重写的isHandler方法如下。从代码可以看到对RequestMappingHandlerMapping类来说,只有bean被@Controller注解或者@RequestMapping注解修饰时才会被扫描。

@Override
protected boolean isHandler(Class beanType) {
    return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
            AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}

上一篇文章提到若Java方法是所需的处理器那么AbstractHandlerMethodMapping类的getMappingForMethod抽象方法为其生成映射并返回,否则返回null。那么为什么只有被@RequestMapping注解修饰的方法才是处理器呢?答案在RequestMappingHandlerMapping类重写的getMappingForMethod方法中:

@Override
protected RequestMappingInfo getMappingForMethod(Method method, Class handlerType) {
    RequestMappingInfo info = createRequestMappingInfo(method);
    if (info != null) {
        RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
        if (typeInfo != null) {
            info = typeInfo.combine(info);
        }
    }
    return info;
}

private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
    RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);
    RequestCondition condition = (element instanceof Class ?
            getCustomTypeCondition((Class) element) : getCustomMethodCondition((Method) element));
    return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null);
}

protected RequestCondition getCustomTypeCondition(Class handlerType) {
    return null;
}

protected RequestCondition getCustomMethodCondition(Method method) {
    return null;
}

protected RequestMappingInfo createRequestMappingInfo(
        RequestMapping requestMapping, RequestCondition customCondition) {

    return RequestMappingInfo
            .paths(resolveEmbeddedValuesInPatterns(requestMapping.path()))
            .methods(requestMapping.method())
            .params(requestMapping.params())
            .headers(requestMapping.headers())
            .consumes(requestMapping.consumes())
            .produces(requestMapping.produces())
            .mappingName(requestMapping.name())
            .customCondition(customCondition)
            .options(this.config)
            .build();
}
  1. createRequestMappingInfo方法的作用:

    • 调用AnnotatedElementUtils.findMergedAnnotation将Java元素(@RequestMapping可以用于类或方法)上的注解合并到@RequestMapping注解上,之所以这么做是因为Spring4.3增加了@GetMapping、@PostMapping等简化@RequestMapping的注解;
    • 可以通过getCustomTypeCondition和getCustomMethodCondition两个方法分别自定义被注解类和被注解方法的请求匹配条件;
    • 若@RequestMapping注解不存在,返回null表明该元素不是处理器,否则返回映射;
  2. getMappingForMethod首先为方法创建映射,若该方法是处理器则为该方法所在类创建映射并合并到方法的映射上。

查找匹配的HandlerMethod

RequestMappingHandlerMapping是如何具体查找匹配处理器方法的呢?上文末尾提到AbstractHandlerMethodMapping类提到getMatchingMapping抽象方法用来检查处理器方法映射是否与请求相匹配,若匹配则返回一个与当前请求有关的映射,否则返回null。RequestMappingHandlerMapping类的getMatchingMapping定义在其父类RequestMappingInfoHandlerMapping类中,代码如下所示:

@Override
protected RequestMappingInfo getMatchingMapping(RequestMappingInfo info, HttpServletRequest request) {
    return info.getMatchingCondition(request);
}

可以看到该方法将判断过程委托给了RequestMappingInfo的getMatchingCondition方法,下面重点看一下RequestMappingInfo类。

RequestMappingInfo类

RequestMappingInfo类表示@RequestMapping注解有关的信息。

成员变量

RequestMappingInfo类的成员变量和构造函数如下所示,可以看到RequestMappingInfo类的成员变量几乎都是RequestCondition,与@RequestMapping注解的各个属性相对应,从变量名即可分辨出用途。

public final class RequestMappingInfo implements RequestCondition {

    private final String name;

    private final PatternsRequestCondition patternsCondition;

    private final RequestMethodsRequestCondition methodsCondition;

    private final ParamsRequestCondition paramsCondition;

    private final HeadersRequestCondition headersCondition;

    private final ConsumesRequestCondition consumesCondition;

    private final ProducesRequestCondition producesCondition;

    private final RequestConditionHolder customConditionHolder;


    public RequestMappingInfo(String name, PatternsRequestCondition patterns, RequestMethodsRequestCondition methods,
            ParamsRequestCondition params, HeadersRequestCondition headers, ConsumesRequestCondition consumes,
            ProducesRequestCondition produces, RequestCondition custom) {

        this.name = (StringUtils.hasText(name) ? name : null);
        this.patternsCondition = (patterns != null ? patterns : new PatternsRequestCondition());
        this.methodsCondition = (methods != null ? methods : new RequestMethodsRequestCondition());
        this.paramsCondition = (params != null ? params : new ParamsRequestCondition());
        this.headersCondition = (headers != null ? headers : new HeadersRequestCondition());
        this.consumesCondition = (consumes != null ? consumes : new ConsumesRequestCondition());
        this.producesCondition = (produces != null ? produces : new ProducesRequestCondition());
        this.customConditionHolder = new RequestConditionHolder(custom);
    }

    public RequestMappingInfo(PatternsRequestCondition patterns, RequestMethodsRequestCondition methods,
            ParamsRequestCondition params, HeadersRequestCondition headers, ConsumesRequestCondition consumes,
            ProducesRequestCondition produces, RequestCondition custom) {

        this(null, patterns, methods, params, headers, consumes, produces, custom);
    }

    public RequestMappingInfo(RequestMappingInfo info, RequestCondition customRequestCondition) {
        this(info.name, info.patternsCondition, info.methodsCondition, info.paramsCondition, info.headersCondition,
                info.consumesCondition, info.producesCondition, customRequestCondition);
    }

    public String getName() {
        return this.name;
    }

    public PatternsRequestCondition getPatternsCondition() {
        return this.patternsCondition;
    }

    public RequestMethodsRequestCondition getMethodsCondition() {
        return this.methodsCondition;
    }

    public ParamsRequestCondition getParamsCondition() {
        return this.paramsCondition;
    }

    public HeadersRequestCondition getHeadersCondition() {
        return this.headersCondition;
    }

    public ConsumesRequestCondition getConsumesCondition() {
        return this.consumesCondition;
    }

    public ProducesRequestCondition getProducesCondition() {
        return this.producesCondition;
    }

    public RequestCondition getCustomCondition() {
        return this.customConditionHolder.getCondition();
    }

    // 省略一些代码
}

合并方法

combine合并方法用来将一个RequestMappingInfo合并到另一个RequestMappingInfo中,如将Controller类的@RequestMapping注解信息合并到该类各方法的@RequestMapping注解上,在RequestMappingHandlerMapping类的getMappingForMethod方法中便做了此操作(见上文)。

/**
 * Combine "this" request mapping info (i.e. the current instance) with another request mapping info instance.
 * 

Example: combine type- and method-level request mappings. * @return a new request mapping info instance; never {@code null} */ @Override public RequestMappingInfo combine(RequestMappingInfo other) { String name = combineNames(other); PatternsRequestCondition patterns = this.patternsCondition.combine(other.patternsCondition); RequestMethodsRequestCondition methods = this.methodsCondition.combine(other.methodsCondition); ParamsRequestCondition params = this.paramsCondition.combine(other.paramsCondition); HeadersRequestCondition headers = this.headersCondition.combine(other.headersCondition); ConsumesRequestCondition consumes = this.consumesCondition.combine(other.consumesCondition); ProducesRequestCondition produces = this.producesCondition.combine(other.producesCondition); RequestConditionHolder custom = this.customConditionHolder.combine(other.customConditionHolder); return new RequestMappingInfo(name, patterns, methods, params, headers, consumes, produces, custom.getCondition()); } private String combineNames(RequestMappingInfo other) { if (this.name != null && other.name != null) { String separator = RequestMappingInfoHandlerMethodMappingNamingStrategy.SEPARATOR; return this.name + separator + other.name; } else if (this.name != null) { return this.name; } else { return other.name; } }

匹配请求

上文提到RequestMappingHandlerMapping类在查找匹配的HandlerMethod时将判断过程委托给了RequestMappingInfo的getMatchingCondition方法,该方法代码如下:

/**
 * Checks if all conditions in this request mapping info match the provided request and returns
 * a potentially new request mapping info with conditions tailored to the current request.
 * 

For example the returned instance may contain the subset of URL patterns that match to * the current request, sorted with best matching patterns on top. * @return a new instance in case all conditions match; or {@code null} otherwise */ @Override public RequestMappingInfo getMatchingCondition(HttpServletRequest request) { RequestMethodsRequestCondition methods = this.methodsCondition.getMatchingCondition(request); ParamsRequestCondition params = this.paramsCondition.getMatchingCondition(request); HeadersRequestCondition headers = this.headersCondition.getMatchingCondition(request); ConsumesRequestCondition consumes = this.consumesCondition.getMatchingCondition(request); ProducesRequestCondition produces = this.producesCondition.getMatchingCondition(request); if (methods == null || params == null || headers == null || consumes == null || produces == null) { return null; } PatternsRequestCondition patterns = this.patternsCondition.getMatchingCondition(request); if (patterns == null) { return null; } RequestConditionHolder custom = this.customConditionHolder.getMatchingCondition(request); if (custom == null) { return null; } return new RequestMappingInfo(this.name, patterns, methods, params, headers, consumes, produces, custom.getCondition()); }

从代码可以看到各个判断条件依次调用了getMatchingCondition接口方法,若匹配则返回条件否则返回null,因此只要这些条件有一个不匹配那么该RequestMappingInfo就不与请求匹配。
以PatternsRequestCondition为例,其getMatchingCondition代码如下:

@Override
public PatternsRequestCondition getMatchingCondition(HttpServletRequest request) {
    if (this.patterns.isEmpty()) {
        return this;
    }

    String lookupPath = this.pathHelper.getLookupPathForRequest(request);
    List matches = getMatchingPatterns(lookupPath);

    return matches.isEmpty() ? null :
        new PatternsRequestCondition(matches, this.pathHelper, this.pathMatcher, this.useSuffixPatternMatch,
                this.useTrailingSlashMatch, this.fileExtensions);
}

public List getMatchingPatterns(String lookupPath) {
    List matches = new ArrayList();
    for (String pattern : this.patterns) {
        String match = getMatchingPattern(pattern, lookupPath);
        if (match != null) {
            matches.add(match);
        }
    }
    Collections.sort(matches, this.pathMatcher.getPatternComparator(lookupPath));
    return matches;
}

private String getMatchingPattern(String pattern, String lookupPath) {
    if (pattern.equals(lookupPath)) {
        return pattern;
    }
    if (this.useSuffixPatternMatch) {
        if (!this.fileExtensions.isEmpty() && lookupPath.indexOf('.') != -1) {
            for (String extension : this.fileExtensions) {
                if (this.pathMatcher.match(pattern + extension, lookupPath)) {
                    return pattern + extension;
                }
            }
        }
        else {
            boolean hasSuffix = pattern.indexOf('.') != -1;
            if (!hasSuffix && this.pathMatcher.match(pattern + ".*", lookupPath)) {
                return pattern + ".*";
            }
        }
    }
    if (this.pathMatcher.match(pattern, lookupPath)) {
        return pattern;
    }
    if (this.useTrailingSlashMatch) {
        if (!pattern.endsWith("/") && this.pathMatcher.match(pattern + "/", lookupPath)) {
            return pattern +"/";
        }
    }
    return null;
}
  • getMatchingCondition方法中先得到该请求的查找路径;
  • 然后利用getMatchingPatterns方法得到与查找路径匹配的所有形式,可以看到getMatchingPattern中使用了useSuffixPatternMatch、fileExtensions和useTrailingSlashMatch属性,这些分别与RequestMappingHandlerMapping类的useSuffixPatternMatch、useRegisteredSuffixPatternMatch和useTrailingSlashMatch属性相对应。
  • 这些属性被赋值是因为在RequestMappingHandlerMapping为Java方法创建RequestMappingInfo对象时由createRequestMappingInfo方法调用了RequestMappingInfo.Builder。

总结

通过两篇文章的深入分析,Spring MVC中@RequestMapping注解背后的原理逐渐明晰,其他HandlerMapping实现类大同小异,在此不再赘述。

你可能感兴趣的:(Spring MVC请求处理(三) - HandlerMapping(二))