spring 5.1.2 mvc RequestMappingHandlerMapping 调用handler过程

https://my.oschina.net/zhangxufeng/blog/2177464

https://www.jianshu.com/p/447826c28e37 Interceptors xml文件中配置

调用gethandler方法,会通过request中的url从之前初始化的methodhander中找到最匹配的一个methodhandler,如果url不能匹配到相应的【mappingRegistry.getMappingsByUrl(lookupPath)】,
则从所有的methodhandler的中找参数,返回值等都匹配的【mappingRegistry.getMappings().keySet()】
找到最匹配的methodhandler后,则

在这之前要看下RequestMappingHandlerMapping -> AbstractHandlerMapping 继承WebApplicationObjectSupport --->ApplicationObjectSupport ->ApplicationContextAware
在初始化时会调用 setApplicationContext initApplicationContext detectMappedInterceptors 会把所有的MappedInterceptor的bean加入到adaptedInterceptors list中 以及<bean <property>> 配置的加入到interceptors中
https://www.jianshu.com/p/447826c28e37

在RequestMappingHandlerMapping初始化的时候相关的interceptors也初始化完毕,然后在找到匹配的methodhandler调用 getHandlerExecutionChain 将相应的interceptors添加到HandlerExecutionChain上,interceptors包括MappedInterceptor以及<bean <property>>配置的,最后getHandler返回这个包含有拦截器的HandlerExecutionChain

//在dispatcher-servlet.xml文件中添加下面代码:

//DefaultInterceptor实现HandlerInterceptor,<mvc:interceptors>配置的时候,直接MappedInterceptor。
<mvc:interceptors>
        <mvc:interceptor>
            <mvc:mapping path="/**"/>
            <bean class="interceptor.DefaultInterceptor">
        </mvc:interceptor>
</mvc:interceptors>

//WebInterceptor实现HandlerInterceptor,<bean <property>>配置的时候,判断后添加到adaptedInterceptors。
<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping">
    <property name="interceptors">
        <list>
            <bean class="interceptor.DefaultInterceptor1" />
        </list>
    </property>
</bean>

上面配置文件中两种拦截器配置方式会产生两个不同的BeanNameUrlHandlerMapping实例,那个拦截器配置在前那个BeanNameUrlHandlerMapping实例执行;这样的话,后面配置的拦截器将不会被执行,所以在配置拦截器的时候选取一种方式即可。

<center>获取Handler的时序图</center>

AbstractHandlerMapping





    /**
     * Initializes the interceptors.
     * @see #extendInterceptors(java.util.List)
     * @see #initInterceptors()
     */
    @Override
    protected void initApplicationContext() throws BeansException {
        //<bean <property>>配置的拦截器如:DefaultInterceptor1
        extendInterceptors(this.interceptors);
        ////<mvc:interceptors>配置的拦截器如:DefaultInterceptor
        detectMappedInterceptors(this.adaptedInterceptors);
         //将<bean <property>>配置的拦截器添加到对应的拦截器List中
        initInterceptors();
    }

    /**
     * Extension hook that subclasses can override to register additional interceptors,
     * given the configured interceptors (see {@link #setInterceptors}).
     * <p>Will be invoked before {@link #initInterceptors()} adapts the specified
     * interceptors into {@link HandlerInterceptor} instances.
     * <p>The default implementation is empty.
     * @param interceptors the configured interceptor List (never {@code null}), allowing
     * to add further interceptors before as well as after the existing interceptors
     */
    protected void extendInterceptors(List<Object> interceptors) {
    }

    /**
     * Detect beans of type {@link MappedInterceptor} and add them to the list of mapped interceptors.
     * <p>This is called in addition to any {@link MappedInterceptor MappedInterceptors} that may have been provided
     * via {@link #setInterceptors}, by default adding all beans of type {@link MappedInterceptor}
     * from the current context and its ancestors. Subclasses can override and refine this policy.
     * @param mappedInterceptors an empty list to add {@link MappedInterceptor} instances to
     */
    protected void detectMappedInterceptors(List<HandlerInterceptor> mappedInterceptors) {
        mappedInterceptors.addAll(
                BeanFactoryUtils.beansOfTypeIncludingAncestors(
                        obtainApplicationContext(), MappedInterceptor.class, true, false).values());
    }

    /**
     * Initialize the specified interceptors, checking for {@link MappedInterceptor MappedInterceptors} and
     * adapting {@link HandlerInterceptor}s and {@link WebRequestInterceptor HandlerInterceptor}s and
     * {@link WebRequestInterceptor}s if necessary.
     * @see #setInterceptors
     * @see #adaptInterceptor
     */
    protected void initInterceptors() {
        if (!this.interceptors.isEmpty()) {
            for (int i = 0; i < this.interceptors.size(); i++) {
                Object interceptor = this.interceptors.get(i);
                if (interceptor == null) {
                    throw new IllegalArgumentException("Entry number " + i + " in interceptors array is null");
                }
                this.adaptedInterceptors.add(adaptInterceptor(interceptor));
            }
        }
    }




    /**
     * Look up a handler for the given request, falling back to the default
     * handler if no specific one is found.
     * @param request current HTTP request
     * @return the corresponding handler instance, or the default handler
     * @see #getHandlerInternal
     */
    @Override
    @Nullable
    public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        Object handler = getHandlerInternal(request);
        if (handler == null) {
            handler = getDefaultHandler();
        }
        if (handler == null) {
            return null;
        }
        // Bean name or resolved handler?
        if (handler instanceof String) {
            String handlerName = (String) handler;
            handler = obtainApplicationContext().getBean(handlerName);
        }


     // 获取当前系统中配置的Interceptor,将其与handler一起封装为一个HandlerExecutionChain
        HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);

        if (logger.isTraceEnabled()) {
            logger.trace("Mapped to " + handler);
        }
        else if (logger.isDebugEnabled() && !request.getDispatcherType().equals(DispatcherType.ASYNC)) {
            logger.debug("Mapped to " + executionChain.getHandler());
        }
    // 这里CorsUtils.isCorsRequest()方法判断的是当前请求是否为一个跨域的请求,如果是一个跨域的请求,
    // 则将跨域相关的配置也一并封装到HandlerExecutionChain中
        if (CorsUtils.isCorsRequest(request)) {
            CorsConfiguration globalConfig = this.corsConfigurationSource.getCorsConfiguration(request);
            CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
            CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
            executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
        }

        return executionChain;
    }



    /**
     * Build a {@link HandlerExecutionChain} for the given handler, including
     * applicable interceptors.
     * <p>The default implementation builds a standard {@link HandlerExecutionChain}
     * with the given handler, the handler mapping's common interceptors, and any
     * {@link MappedInterceptor MappedInterceptors} matching to the current request URL. Interceptors
     * are added in the order they were registered. Subclasses may override this
     * in order to extend/rearrange the list of interceptors.
     * <p><b>NOTE:</b> The passed-in handler object may be a raw handler or a
     * pre-built {@link HandlerExecutionChain}. This method should handle those
     * two cases explicitly, either building a new {@link HandlerExecutionChain}
     * or extending the existing chain.
     * <p>For simply adding an interceptor in a custom subclass, consider calling
     * {@code super.getHandlerExecutionChain(handler, request)} and invoking
     * {@link HandlerExecutionChain#addInterceptor} on the returned chain object.
     * @param handler the resolved handler instance (never {@code null})
     * @param request current HTTP request
     * @return the HandlerExecutionChain (never {@code null})
     * @see #getAdaptedInterceptors()
     */
    protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
        HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
                (HandlerExecutionChain) handler : new HandlerExecutionChain(handler));

        String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
        for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
            if (interceptor instanceof MappedInterceptor) {
                MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
                //<mvc:interceptors>都会被解析成MappedInterceptor 如果匹配路径,则加入
                if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
                    chain.addInterceptor(mappedInterceptor.getInterceptor());
                }
            }
            else {
                //将<bean <property>>配置的拦截器添加到对应的拦截器List中
                chain.addInterceptor(interceptor);
            }
        }
        return chain;
    }


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

AbstractHandlerMethodMapping




    // Handler method lookup

    /**
     * Look up a handler method for the given request.
     */
    @Override
    protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
        String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
        this.mappingRegistry.acquireReadLock();
        try {
            HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
            return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
        }
        finally {
            this.mappingRegistry.releaseReadLock();
        }
    }

    /**
     * Look up the best-matching handler method for the current request.
     * If multiple matches are found, the best match is selected.
     * @param lookupPath mapping lookup path within the current servlet mapping
     * @param request the current request
     * @return the best-matching handler method, or {@code null} if no match
     * @see #handleMatch(Object, String, HttpServletRequest)
     * @see #handleNoMatch(Set, String, HttpServletRequest)
     */
    @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()) {
            // No choice but to go through all mappings...
                        // 如果无法通过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);
            Match bestMatch = matches.get(0);
            if (matches.size() > 1) {

            // 如果匹配结果不止一个,首先会判断是否是跨域请求,如果是,
            // 则返回PREFLIGHT_AMBIGUOUS_MATCH,如果不是,则会判断前两个匹配程度是否相同,
            // 如果相同则抛出异常
                if (logger.isTraceEnabled()) {
                    logger.trace(matches.size() + " matching mappings: " + matches);
                }
                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();
                    String uri = request.getRequestURI();
                    throw new IllegalStateException(
                            "Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}");
                }
            }
            handleMatch(bestMatch.mapping, lookupPath, request);
            return bestMatch.handlerMethod;
        }
        else {
            return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
        }
    }

    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)));
            }
        }
    }



    /**
     * Invoked when a matching mapping is found.
     * @param mapping the matching mapping
     * @param lookupPath mapping lookup path within the current servlet mapping
     * @param request the current request
     */
    protected void handleMatch(T mapping, String lookupPath, HttpServletRequest request) {
        request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, lookupPath);
    }


    /**
     * A thin wrapper around a matched HandlerMethod and its mapping, for the purpose of
     * comparing the best match with a comparator in the context of the current request.
     */
    private class Match {

        private final T mapping;

        private final HandlerMethod handlerMethod;

        public Match(T mapping, HandlerMethod handlerMethod) {
            this.mapping = mapping;
            this.handlerMethod = handlerMethod;
        }

        @Override
        public String toString() {
            return this.mapping.toString();
        }
    }




    //RequestMappingInfoHandlerMapping
    /**
     * Check if the given RequestMappingInfo matches the current request and
     * return a (potentially new) instance with conditions that match the
     * current request -- for example with a subset of URL patterns.
     * @return an info in case of a match; or {@code null} otherwise.
     */
    @Override
    protected RequestMappingInfo getMatchingMapping(RequestMappingInfo info, HttpServletRequest request) {
        return info.getMatchingCondition(request);
    }




    //RequestMappingInfo
    /**
     * 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.
     * <p>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
    @Nullable
    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());
    }

HandlerMethod

    public HandlerMethod createWithResolvedBean() {
        Object handler = this.bean;
        if (this.bean instanceof String) {
            Assert.state(this.beanFactory != null, "Cannot resolve bean name without BeanFactory");
            String beanName = (String)this.bean;
            handler = this.beanFactory.getBean(beanName);
        }

        return new HandlerMethod(this, handler);
    }



    private HandlerMethod(HandlerMethod handlerMethod, Object handler) {
        Assert.notNull(handlerMethod, "HandlerMethod is required");
        Assert.notNull(handler, "Handler object is required");
        this.bean = handler;
        this.beanFactory = handlerMethod.beanFactory;
        this.beanType = handlerMethod.beanType;
        this.method = handlerMethod.method;
        this.bridgedMethod = handlerMethod.bridgedMethod;
        this.parameters = handlerMethod.parameters;
        this.responseStatus = handlerMethod.responseStatus;
        this.responseStatusReason = handlerMethod.responseStatusReason;
        this.resolvedFromHandlerMethod = handlerMethod;
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容