spring源码------一个请求在spring中的处理过程(请求的处理链HandlerExecutionChain的选择)代码及流程图说明 (3)

前提

 前面已经讲过了Servlet规范到FrameworkServlet,以及FrameworkServlet规范到DispatcherServlet。其中在DispatcherServlet这个类中讲到了doDispatch方法,其中的第一个重要步骤就是,调用getHandler方法根据请求对象获取合适的HandlerExecutionChain

    protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        //判断handlerMappings是否为空,默认不会为空的
        if (this.handlerMappings != null) {
            //迭代handlerMappings
            for (HandlerMapping mapping : this.handlerMappings) {
                //调用HandlerMapping的getHandler方法获取handler,这里只获取到第一个合适的HandlerExecutionChain,所以有顺序优先性
                HandlerExecutionChain handler = mapping.getHandler(request);
                if (handler != null) {
                    return handler;
                }
            }
        }
        return null;
    }

 这里有两个关键的东西:

  1. 包含HandlerMapping对象集合的handlerMappings
  2. 返回的HandlerExecutionChain

1.HandlerMapping何时被封装到handlerMappings

handlerMappings是在容器刷新上下文的时候会调用的,具体的可以看看这个spring什么时候以及何时初始化web应用相关上下文的(FrameworkServlet,DispatcherServlet)
这里直接看initStrategies方法中的initHandlerMappings方法逻辑。

    private void initHandlerMappings(ApplicationContext context) {
        this.handlerMappings = null;
        //是否获取容器中的所有的HandlerMapping
        if (this.detectAllHandlerMappings) {
            //获取容器中的所有的HandlerMapping
            Map<String, HandlerMapping> matchingBeans =
                    BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
            //如果存在HandlerMapping,则进行排序
            if (!matchingBeans.isEmpty()) {
                this.handlerMappings = new ArrayList<>(matchingBeans.values());
                // We keep HandlerMappings in sorted order.
                AnnotationAwareOrderComparator.sort(this.handlerMappings);
            }
        }
        else {
            try {
                //获取上下文中的bean的名字是handlerMapping的类型为HandlerMapping的bean
                HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
                this.handlerMappings = Collections.singletonList(hm);
            }
            catch (NoSuchBeanDefinitionException ex) {
                // Ignore, we'll add a default HandlerMapping later.
            }
        }
                //如果容器中没有,则用默认的,一般情况下不会到这一波,关于默认的配置会读取DispatcherServlet.properties配置文件中的配置

        if (this.handlerMappings == null) {
            this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
            if (logger.isTraceEnabled()) {
                logger.trace("No HandlerMappings declared for servlet '" + getServletName() +
                        "': using default strategies from DispatcherServlet.properties");
            }
        }
    }

 整体的逻辑还是比较简单的,就是从容器中获取所有的HandlerMapping类型的bean封装到一个集合中,然后在进行排序。

2. HandlerExecutionChain对象

HandlerExecutionChain主要是一个由Object类型的(==默认情况下是HandlerMethod对象==)的handler字段跟两个由HandlerInterceptor对象组成的拦截器数组跟集合组成的

public class HandlerExecutionChain {
    //默认情况下这个Object是HandlerMethod类型的
    private final Object handler;

    @Nullable
    private HandlerInterceptor[] interceptors;

    @Nullable
    private List<HandlerInterceptor> interceptorList;
}

3 如何获取HandlerExecutionChain对象的

 我们直接进入到DispatcherServlet类的getHandler方法中。

    protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        //判断handlerMappings是否为空,默认不会为空的
        if (this.handlerMappings != null) {
            //迭代handlerMappings
            for (HandlerMapping mapping : this.handlerMappings) {
                //调用HandlerMapping的getHandler方法获取handler,这里只获取到第一个合适的HandlerExecutionChain,所以有顺序优先性
                HandlerExecutionChain handler = mapping.getHandler(request);
                if (handler != null) {
                    return handler;
                }
            }
        }
        return null;
    }

 这里需要注意的一点事在循环遍历HandlerMapping对象集合的时候,如果找到了第一个合适的就会停下来,直接返回。所以一般自定义的实现类都会实现Ordred接口或者贴上@Order注解来指定对应的顺序。

3.1 进入到AbstractHandlerMappinggetHandler方法

HandlerMappinggetHandler方法由AbstractHandlerMapping实现的。

        public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        //获取handler
        Object handler = getHandlerInternal(request);
        //如果没有找到合适的处理器,就用默认的正常情况下是null
        if (handler == null) {
            handler = getDefaultHandler();
        }
        //如果没有handel则直接返回
        if (handler == null) {
            return null;
        }
        // Bean name or resolved handler?
        //如果对应的HandlerMethod是string类型的则需要确认是不是Bean,并寻找对应的bean
        if (handler instanceof String) {
            String handlerName = (String) handler;
            handler = obtainApplicationContext().getBean(handlerName);
        }
        //添加符合当前请求路径的拦截器到一个新的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());
        }
        //这个部分的逻辑不太清楚,大概跟跨域请求相关的,如果handler有跟跨域先关的配置(实现了CorsConfigurationSource接口)
        if (hasCorsConfigurationSource(handler)) {
            CorsConfiguration config = (this.corsConfigurationSource != null ? this.corsConfigurationSource.getCorsConfiguration(request) : null);
            CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
            config = (config != null ? config.combine(handlerConfig) : handlerConfig);
            executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
        }

        return executionChain;
    }

 这个方法的逻辑就是获取导一个请求处理器对象:

  1. 调用由子类来实现的getHandlerInternal方法,获取一个合适的Object类型的handler(默认情况下是AbstractHandlerMethodMapping实现的返回的是HandlerMethod类型)
  2. 如果没有合适的则用默认的,默认情况下是默认的也是null
  3. 如果是handler是null,则直接结束当前方法
  4. 如果hanler是String类型的,则可能表示的是一个bean的名称,所以前去上下文中去取
  5. 根据请求的路径获取handler中的合适的拦截器,然后加入到创建的HandlerExecutionChain对象中,并返回
  6. 关于跨域先关的处理逻辑,这一部分没太理解
3.2.1 获取handler

 在上面的步骤中最关键的就是第一步的获取合适请求处理器。第一步的方法getHandlerInternal是在AbstractHandlerMapping中定义的由子类来实现的一个方法。

3.2.1.1 getHandlerInternal方法的实现类

 在spring中AbstractHandlerMapping类的getHandlerInternal有很多实现比如AbstractUrlHandlerMappingAbstractHandlerMethodMapping等,但是哪一个才是默认的呢。这里直接说。
 在Spring中有一个类DelegatingWebMvcConfiguration,这个类会被容器管理,而这个类的父类WebMvcConfigurationSupport中会用@Bean注解注入多个AbstractHandlerMapping的子类到到容器中比如,RequestMappingHandlerMappingBeanNameUrlHandlerMappingSimpleUrlHandlerMapping等。


@Bean
    public BeanNameUrlHandlerMapping beanNameHandlerMapping(
    ......
        return mapping;
    }
    
    @Bean
    public RequestMappingHandlerMapping requestMappingHandlerMapping(
            ContentNegotiationManager mvcContentNegotiationManager,
            FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) {

        RequestMappingHandlerMapping mapping = createRequestMappingHandlerMapping();
        mapping.setOrder(0);
        mapping.setInterceptors(getInterceptors(mvcConversionService, mvcResourceUrlProvider));
        mapping.setContentNegotiationManager(mvcContentNegotiationManager);
        mapping.setCorsConfigurations(getCorsConfigurations());
        ......
        return mapping;
    }

 接下来的分析我们选取的是RequestMappingHandlerMapping这个类进行分析的,其他类的这个方法的目的都是一样的,大同小异,可以自己查看源码

3.2.1.2 寻找内部HandlerMethodgetHandlerInternal方法(这里举例的是RequestMappingHandlerMapping

 在RequestMappingHandlerMapping中实现的getHandlerInternal方法回先调用父类AbstractHandlerMethodMapping的实现,然后执行之际的额外逻辑。所以先看看AbstractHandlerMethodMapping的逻辑

    protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
        //获取映射查找路径 比如 localhost/one/two 对应的lookupPath就是/one/two
        String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
        //将请求的路径加入到请求对象中
        request.setAttribute(LOOKUP_PATH, lookupPath);
        //获取读锁
        this.mappingRegistry.acquireReadLock();
        try {
            //根据路径查找handlerMethod
            HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
            //如果handlerMethod不是null,并且中的bean是容器中的Bean则需要先获取
            return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
        }
        finally {
            //释放锁
            this.mappingRegistry.releaseReadLock();
        }
    }

 主要逻辑就是先获取请求的请求路径,然后先获取一个读锁根据请求的路径去找对应的HandlerMethod,找到之后就返回,没有找到就返回null,最后释放读锁。

3.2.2 如何根据请求的路径获取HandlerMethod

 接下来的逻辑就是如何根据请求的路径来获取HandlerMethod了。

    protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
    //Match对象保存了路径跟对应的handlerMethod的包装类
        List<Match> matches = new ArrayList<>();
                //根据路径查找对应匹配的路径集合,mappingRegistry会在AbstractHandlerMethodMapping初始化之后设置,MappingRegistry是一个注册表对象,它维护所有到处理程序方法的映射
        List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
        //对应的匹配的路径集合不为空,将路径跟请求跟对应的handleMethod保存到Match中
        if (directPathMatches != null) {
            addMatchingMappings(directPathMatches, matches, request);
        }
        //匹配器为空说明没有合适的mapping跟当前的request匹配,就把所有的mapping跟当前请求进行匹配选择
        if (matches.isEmpty()) {
            // No choice but to go through all mappings...
            addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
        }
        //如果找到了合适的就进行下一步处理
        if (!matches.isEmpty()) {
            //获取一个MatchComparator
            Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
            //进行排序
            matches.sort(comparator);
            //选出最佳的匹配
            Match bestMatch = matches.get(0);
            if (matches.size() > 1) {
                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 + "}");
                }
            }
            //设置匹配的处理方法
            request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);
            //从mappingInfo中获取URI模板变量、矩阵变量和可产生的媒体类型
            handleMatch(bestMatch.mapping, lookupPath, request);
            //返回HandlerMethod
            return bestMatch.handlerMethod;
        }
        else {
            //没有就将所有的匹配路径交给子类在遍历一边,如果还是寻找不出来,就根据不同情况抛出对应的异常
            return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
        }
    }

 现在对逻辑进行分析:

  1. 从处理方法跟路径的注册表中,根据请求的路径获取对应的映射集合
  2. 如果匹配的路径不为空将对应的路径跟匹配的HandlerMethod加入到路径跟处理器的包装类集合matches
  3. 如果matches为空,说明没有合适的mapping匹配的,因此要遍历所有的mapping进行寻找
  4. 如果找到了,先获取一个合适MatchComparator(用于后面的匹配HandlerMethod用),然后将matches中的第一个跟第二个进行比较获取合适的HandlerMethod。获取到合适的之后将对应的HandlerMethod设置到request的属性中
  5. 如果没有找到,就将所有的匹配路径交给子类在遍历一边,如果还是寻找不出来,就根据不同情况抛出对应的异常
3.2.2.1 注册列表mappingRegistry是何时完成映射的注册的

 在上面的方法中一个核心的对象就是MappingRegistry这个注册列表。这个对象保存了对应的请求跟处理方法和其他配置。
AbstractHandlerMethodMapping实现了InitializingBeanafterPropertiesSet方法,在初始化之后会调用这个方法。

    public void afterPropertiesSet() {
        //初始化映射的方法
        initHandlerMethods();
    }


    protected void initHandlerMethods() {
        //确定应用程序上下文中候选bean的名称
        for (String beanName : getCandidateBeanNames()) {
            //如果bean的名称不是以scopedTarget开头,这个开头的bean是被代理的有作用域的bean,这种情况下需要排除
            if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
                //处理候选bean
                processCandidateBean(beanName);
            }
        }
        //初始化handlerMethod
        handlerMethodsInitialized(getHandlerMethods());
    }


    protected String[] getCandidateBeanNames() {
        //是object类型的都拿出来
        return (this.detectHandlerMethodsInAncestorContexts ?
                BeanFactoryUtils.beanNamesForTypeIncludingAncestors(obtainApplicationContext(), Object.class) :
                obtainApplicationContext().getBeanNamesForType(Object.class));
    }


    protected void processCandidateBean(String beanName) {
        Class<?> beanType = null;
        try {
            //获取bean的Class对象
            beanType = obtainApplicationContext().getType(beanName);
        }
        catch (Throwable ex) {
            // An unresolvable bean type, probably from a lazy bean - let's ignore it.
            if (logger.isTraceEnabled()) {
                logger.trace("Could not resolve type for bean '" + beanName + "'", ex);
            }
        }
        //如果bean不为空,并且是符合的类型,其中的isHandler在RequestMappingHandlerMapping实现
        if (beanType != null && isHandler(beanType)) {
            //对bean进行注册
            detectHandlerMethods(beanName);
        }
    }


    protected boolean isHandler(Class<?> beanType) {
    //bean上面有Controller注解或者RequestMapping类型注解
        return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
                AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
    }

 上面逻辑中主要就是在上下文中寻找所有的bean,然后对bean进行筛选需要满足以下条件的bean:

  1. bean是不是被代理出来的有作用域的bean
  2. bean的Class对象不能为null
  3. bean的Class对象有Controller注解或者RequestMapping类型注解(注意这里的isHandler具体是现在RequestMappingHandlerMapping中)

 然后就是对bean的注册处理了,处理的方法就在detectHandlerMethods方法中

    protected void detectHandlerMethods(Object handler) {
        //如果对应的handler是String类型的,则从上下文中获取对应的name为handler 的bean 的类型,不是String则直接获取对应的Class对象
        Class<?> handlerType = (handler instanceof String ?
                obtainApplicationContext().getType((String) handler) : handler.getClass());
        //如果类型不是null
        if (handlerType != null) {
            //获取原始Class,因为这个Class可能是经过CGLIB代理过后的
            Class<?> userType = ClassUtils.getUserClass(handlerType);
                //获取对应的Class中的贴有RequestMapping或者派生于RequestMapping注解的方法,然后处理封装为RequestMappingInfo对象,跟对饮给的处理方法组成一个map对象
            //RequestMappingInfo对象包含注解中的一些参数设置信息
            Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
                    (MethodIntrospector.MetadataLookup<T>) method -> {
                        try {
                            return getMappingForMethod(method, userType);
                        }
                        catch (Throwable ex) {
                            throw new IllegalStateException("Invalid mapping on handler class [" +
                                    userType.getName() + "]: " + method, ex);
                        }
                    });
            if (logger.isTraceEnabled()) {
                logger.trace(formatMappings(userType, methods));
            }
            //对方法进行迭代,然后注册
            methods.forEach((method, mapping) -> {
                //获取可调用的方法
                Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
                //注册到mappingRegistry对象中
                registerHandlerMethod(handler, invocableMethod, mapping);
            });
        }
    }

 这里的逻辑就是从对应的请求处理类的Class对象中获取所有的RequestMapping注解的方法转化成一个MethodRequestMappingInfo组成的map集合,然后进行处理。具体的获取方法跟封住逻辑可以自行查看,都是对反射的利用。
 上面就是对应的MappingRegistry注册列表的生成逻辑了。

3.2.3 生成HandlerExecutionChain对象的getHandlerExecutionChain

 现在我们要回到AbstractHandlerMappinggetHandler方法了。上面的逻辑都是分析的这个方法中的getHandlerInternal方法的逻辑,现在看下一个关键步骤HandlerExecutionChain对象的生成方法getHandlerExecutionChain
 这个其实比较简单就是对interceptor的一个筛选过程。

protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
        //检查handler是不是HandlerExecutionChain子类,不是则需要创建
        HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
                (HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
        //获取请求的路径
        String lookupPath = this.urlPathHelper.getLookupPathForRequest(request, LOOKUP_PATH);
        //循环对应的HandlerInterceptor
        for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
            //如果是MappedInterceptor,需要对路径进行匹配不是的则直接添加
            if (interceptor instanceof MappedInterceptor) {
                MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
                //检查路径是不是以下请求“/actuator/*”或者“/error”,不是则将对应的MappedInterceptor加入到HandlerExecutionChain中
                if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
                    chain.addInterceptor(mappedInterceptor.getInterceptor());
                }
            }
            else {
                chain.addInterceptor(interceptor);
            }
        }
        return chain;
    }

4.总结

整体的逻辑有部分代码省略了,但是逻辑还是有点多的,这里做一个大概的总结:

  1. DispatchServletinitHandlerMappings方法中,会从容器中获取到所有的HandlerMapping类型的bean,然后==按照顺序==进行排序
  2. DispatchServletgetHandler方法中,会调用对应的HandlerMappinggetHandler方法获取HandlerExecutionChain对象
    2.1 进入到对应的HandlerMappinggetHandler方法(这里spring中的HandlerMappinggetHandler方法都是调用的AbstractHandlerMapping类的),回先调用子类实现的getHandlerInternal方法获取合适的处理对象(这里举例的是RequestMappingHandlerMapping
    2.2 获取到了对应的请求处理对象后,用将请求对象封装在一个HandlerExecutionChain对象中,并将合适的拦截器集合也封装在一起,然后返回一个HandlerExecutionChain
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 195,783评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,360评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 142,942评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,507评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,324评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,299评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,685评论 3 386
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,358评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,652评论 1 293
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,704评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,465评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,318评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,711评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,991评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,265评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,661评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,864评论 2 335