Spring MVC之RequestMappingHandlerMapping匹配

       对于RequestMappingHandlerMapping,使用Spring的同窗基本都不会陌生,该类的做用有两个:html

  • 经过request查找对应的HandlerMethod,即当前request具体是由Controller中的哪一个方法进行处理;
  • 查找当前系统中的Interceptor,将其与HandlerMethod封装为一个HandlerExecutionChain。

本文主要讲解RequestMappingHandlerMapping是如何获取HandlerMethod和Interceptor,而且将其封装为HandlerExecutionChain的。java

1.总体封装结构

       RequestMappingHandlerMapping实现了HandlerMapping接口,该接口的主要方法以下:跨域

public interface HandlerMapping {
    // 经过request获取HandlerExecutionChain对象
	HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
}

       这里咱们直接看RequestMappingHandlerMapping是如何实现该接口的:app

@Override
@Nullable
public final HandlerExecutionChain getHandler(HttpServletRequest request) 
    throws Exception {
    // 经过request获取具体的处理bean,这里handler可能有两种类型:HandlerMethod和String。
    // 若是是String类型,那么就在BeanFactory中查找该String类型的bean,须要注意的是,返回的
    // bean若是是须要使用RequestMappingHandlerAdapter处理,那么也必须是HandlerMethod类型的
    Object handler = getHandlerInternal(request);
    if (handler == null) {
        // 若是找不处处理方法,则获取自定义的默认handler
        handler = getDefaultHandler();
    }
    if (handler == null) {
        return null;
    }
    if (handler instanceof String) {
        // 若是获取的handler是String类型的,则在当前BeanFactory中获取该名称的bean,
        // 并将其做为handler返回
        String handlerName = (String) handler;
        handler = obtainApplicationContext().getBean(handlerName);
    }

    // 获取当前系统中配置的Interceptor,将其与handler一块儿封装为一个HandlerExecutionChain
    HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
    // 这里CorsUtils.isCorsRequest()方法判断的是当前请求是否为一个跨域的请求,若是是一个跨域的请求,
    // 则将跨域相关的配置也一并封装到HandlerExecutionChain中
    if (CorsUtils.isCorsRequest(request)) {
        CorsConfiguration globalConfig = 
            this.globalCorsConfigSource.getCorsConfiguration(request);
        CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
        CorsConfiguration config = (globalConfig != null ? 
            globalConfig.combine(handlerConfig) : handlerConfig);
        executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
    }
    return executionChain;
}

       从上面的代码能够看出,对于HandlerExecutionChain的获取,RequestMappingHandlerMapping首先会获取当前request对应的handler,而后将其与Interceptor一块儿封装为一个HandlerExecutionChain对象。这里在进行封装的时候,Spring会对当前request是否为跨域请求进行判断,若是是跨域请求,则将相关的跨域配置封装到HandlerExecutionChain中,关于跨域请求,读者能够阅读跨域资源共享 CORS 详解cors

2. 获取HandlerMethod

       关于RequestMappingHandlerMapping是如何获取handler的,其主要在getHandlerInternal()方法中,以下是该方法的源码:ide

@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
    // 获取当前request的URI
    String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
    if (logger.isDebugEnabled()) {
        logger.debug("Looking up handler method for path " + lookupPath);
    }
    // 获取注册的Mapping的读锁
    this.mappingRegistry.acquireReadLock();
    try {
        // 经过path和request查找具体的HandlerMethod
        HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
        if (logger.isDebugEnabled()) {
            if (handlerMethod != null) {
                logger.debug("Returning handler method [" + handlerMethod + "]");
            } else {
                logger.debug("Did not find handler method for [" + lookupPath + "]");
            }
        }
        // 若是获取到的bean是一个String类型的,则在BeanFactory中查找该bean,
        // 并将其封装为一个HandlerMethod对象
        return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
    } finally {
        // 释放当前注册的Mapping的读锁
        this.mappingRegistry.releaseReadLock();
    }
}

       上述方法中,其首先会获取当前request的uri,而后经过uri查找HandlerMethod,而且在最后,会判断获取到的HandlerMethod中的bean是否为String类型的,若是是,则在当前BeanFactory中查找该名称的bean,而且将其封装为HandlerMethod对象。这里咱们直接阅读lookupHandlerMethod()方法:ui

@Nullable
protected HandlerMethod lookupHandlerMethod(String lookupPath, 
      HttpServletRequest request) throws Exception {
    List<Match> matches = new ArrayList<>();
    // 经过uri直接在注册的RequestMapping中获取对应的RequestMappingInfo列表,须要注意的是,
    // 这里进行查找的方式只是经过url进行查找,可是具体哪些RequestMappingInfo是匹配的,还须要进一步过滤
    List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
    if (directPathMatches != null) {
        // 对获取到的RequestMappingInfo进行进一步过滤,而且将过滤结果封装为一个Match列表
        addMatchingMappings(directPathMatches, matches, request);
    }
    if (matches.isEmpty()) {
        // 若是没法经过uri进行直接匹配,则对全部的注册的RequestMapping进行匹配,这里没法经过uri
        // 匹配的状况主要有三种:
        // ①在RequestMapping中定义的是PathVariable,如/user/detail/{id};
        // ②在RequestMapping中定义了问号表达式,如/user/?etail;
        // ③在RequestMapping中定义了*或**匹配,如/user/detail/**
        addMatchingMappings(this.mappingRegistry.getMappings().keySet(), 
            matches, request);
    }

    if (!matches.isEmpty()) {
        // 对匹配的结果进行排序,获取类似度最高的一个做为结果返回,这里对类似度的判断时,
        // 会判断前两个是否类似度是同样的,若是是同样的,则直接抛出异常,若是不相同,
        // 则直接返回最高的一个
        Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
        matches.sort(comparator);
        if (logger.isTraceEnabled()) {
            logger.trace("Found " + matches.size() 
                         + " matching mapping(s) for [" + lookupPath + "] : " + matches);
        }
        // 获取匹配程度最高的一个匹配结果
        Match bestMatch = matches.get(0);
        if (matches.size() > 1) {
            // 若是匹配结果不止一个,首先会判断是不是跨域请求,若是是,
            // 则返回PREFLIGHT_AMBIGUOUS_MATCH,若是不是,则会判断前两个匹配程度是否相同,
            // 若是相同则抛出异常
            if (CorsUtils.isPreFlightRequest(request)) {
                return PREFLIGHT_AMBIGUOUS_MATCH;
            }
            Match secondBestMatch = matches.get(1);
            if (comparator.compare(bestMatch, secondBestMatch) == 0) {
                Method m1 = bestMatch.handlerMethod.getMethod();
                Method m2 = secondBestMatch.handlerMethod.getMethod();
                throw new IllegalStateException("Ambiguous handler methods mapped for" 
                    + " HTTP path '" + request.getRequestURL() + "': {" + m1 
                    + ", " + m2 + "}");
            }
        }
        // 这里主要是对匹配结果的一个处理,主要包含对传入参数和返回的MediaType的处理
        handleMatch(bestMatch.mapping, lookupPath, request);
        return bestMatch.handlerMethod;
    } else {
        // 若是匹配结果是空的,则对全部注册的Mapping进行遍历,判断当前request具体是哪一种状况致使
        // 的没法匹配:①RequestMethod没法匹配;②Consumes没法匹配;③Produces没法匹配;
        // ④Params没法匹配
        return handleNoMatch(this.mappingRegistry.getMappings().keySet(), 
            lookupPath, request);
    }
}

       这里对于结果的匹配,首先会经过uri进行直接匹配,若是能匹配到,则在匹配结果中尝试进行RequestMethod,Consumes和Produces等配置的匹配;若是经过uri不能匹配到,则直接对全部定义的RequestMapping进行匹配,这里主要是进行正则匹配,若是能匹配到。若是可以匹配到,则对匹配结果按照类似度进行排序,而且对前两个结果类似度进行比较,若是类似度同样,则抛出异常,若是不同,则返回类似度最高的一个匹配结果。若是没法获取到匹配结果,则对全部的匹配结果进行遍历,判断当前request具体是哪一部分参数没法匹配到结果。对于匹配结果的获取,主要在addMatchingMappings()方法中,这里咱们继续阅读该方法的源码:this

private void addMatchingMappings(Collection<T> mappings, List<Match> matches, 
      HttpServletRequest request) {
    for (T mapping : mappings) {
        T match = getMatchingMapping(mapping, request);
        if (match != null) {
            matches.add(new Match(match, 
                this.mappingRegistry.getMappings().get(mapping)));
        }
    }
}

       对于RequestMapping的匹配,这里逻辑比较简单,就是对全部的RequestMappingInfo进行遍历,而后将request分别于每一个RequestMappingInfo进行匹配,若是匹配上了,其返回值就不为空,最后将全部的匹配结果返回。以下是getMatchingMapping()方法的源码(其最终调用的是RequestMappingInfo.getMatchingCondition()方法):url

@Override
@Nullable
public RequestMappingInfo getMatchingCondition(HttpServletRequest request) {
    // 判断request请求的类型是否与当前RequestMethod匹配
    RequestMethodsRequestCondition methods = 
        this.methodsCondition.getMatchingCondition(request);
    // 判断request请求的参数是否与RequestMapping中params参数配置的一致
    ParamsRequestCondition params = this.paramsCondition.getMatchingCondition(request);
    // 判断request请求的headers是否与RequestMapping中headers参数配置的一致
    HeadersRequestCondition headers = this.headersCondition.getMatchingCondition(request);
    // 判断request的请求体类型是否与RequestMapping中配置的consumes参数配置的一致
    ConsumesRequestCondition consumes = 
        this.consumesCondition.getMatchingCondition(request);
    // 判断当前RequestMapping将要返回的请求体类型是否与request中Accept的header指定的一致
    ProducesRequestCondition produces = 
        this.producesCondition.getMatchingCondition(request);

    // 对于上述几个判断,若是匹配上了,那么其返回值都不会为空,于是这里会对每一个返回值都进行判断,
    // 若是有任意一个为空,则说明没匹配上,那么就返回null
    if (methods == null || params == null || headers == null 
        || consumes == null || produces == null) {
        return null;
    }

    // 对于前面的匹配,都是一些静态属性的匹配,其中最重要的uri的匹配,主要是正则匹配,
    // 就是在下面这个方法中进行的
    PatternsRequestCondition patterns = 
        this.patternsCondition.getMatchingCondition(request);
    // 若是URI没匹配上,则返回null
    if (patterns == null) {
        return null;
    }

    // 这里主要是对用户自定义的匹配条件进行匹配
    RequestConditionHolder custom = 
        this.customConditionHolder.getMatchingCondition(request);
    if (custom == null) {
        return null;
    }

    // 若是上述全部条件都匹配上了,那么就将匹配结果封装为一个RequestMappingInfo返回
    return new RequestMappingInfo(this.name, patterns, methods, params, headers, 
        consumes, produces, custom.getCondition());
}

        能够看到,对于一个RequestMapping的匹配,主要包括:RequestMethod,Params,Headers,Consumes,Produces,Uri和自定义条件的匹配,若是这几个条件都匹配上了,才能代表当前RequestMapping与request匹配上了。debug

3. Interceptor的封装

      关于Inteceptor的封装,由前述第一点能够看出,其主要在getHandlerExecutionChain()方法中,以下是该方法的源码:

protected HandlerExecutionChain getHandlerExecutionChain(Object handler, 
        HttpServletRequest request) {
    // 将当前handler封装到HandlerExecutionChain对象中
    HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
        (HandlerExecutionChain) handler : new HandlerExecutionChain(handler));

    // 获取当前request的URI,用于MappedInterceptor的匹配
    String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
    // 对当前全部注册的Interceptor进行遍历,若是其是MappedInterceptor类型,则调用其matches()
    // 方法,判断当前Interceptor是否可以应用于该request,若是能够,则添加到HandlerExecutionChain中
    for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
        if (interceptor instanceof MappedInterceptor) {
            MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
            if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
                chain.addInterceptor(mappedInterceptor.getInterceptor());
            }
        } else {
            // 若是当前Interceptor不是MappedInterceptor类型,则直接将其添加到
            // HandlerExecutionChain中
            chain.addInterceptor(interceptor);
        }
    }
    return chain;
}

       对于拦截器,理论上,Spring是会将全部的拦截器都进行一次调用,对因而否须要进行拦截,都是用户自定义实现的。这里若是对于URI有特殊的匹配,能够使用MappedInterceptor,而后实现其matches()方法,用于判断当前MappedInterceptor是否可以应用于当前request。

4. 小结

       本文首先讲解了Spring是如何经过request进行匹配,从而找到具体处理当前请求的RequestMapping的,而后讲解了Spring是如何封装Interceptor,将HandlerMethod和Interceptor封装为一个HandlerExecutionChain的。

相关文章
相关标签/搜索