02-SpringMVC初始化HandlerMappings過(guò)程分析

要了解DispatcherServlet,就必須先了解幾個(gè)特殊Bean,否則在理解源碼的過(guò)程中會(huì)很吃力。
[圖片上傳失敗...(image-11609-1581567742949)]
上圖出自Spring的官方文檔。從圖中可以看出HandlerMapping占的篇幅很大慧域,也是整個(gè)DispatcherServlet映射尋址的關(guān)鍵。我們今天就來(lái)分析在初始化過(guò)程中是如何初始化HandlerMapping的浪读。
在正式開(kāi)始前昔榴,我們先來(lái)了解一個(gè)SpringMVC包中的配置文件


DispatcherServletProcess.png

這個(gè)配置文件是Spring初始化時(shí)的默認(rèn)配置敦冬,如果用戶(hù)沒(méi)有進(jìn)行自定義配置键思,那么會(huì)根據(jù)上面這個(gè)配置中的地址去獲取默認(rèn)處理類(lèi)。
上篇文章中我們已經(jīng)介紹了DispatcherServlet#onRefresh時(shí)每個(gè)方法的作用纲缓,現(xiàn)在我們直入主題痘拆,來(lái)分析分析initHandlerMappings這個(gè)方法都做了什么仰禽。

/**
     * Initialize the HandlerMappings used by this class.
     * <p>If no HandlerMapping beans are defined in the BeanFactory for this namespace,
     * we default to BeanNameUrlHandlerMapping.
     */
    private void initHandlerMappings(ApplicationContext context) {
        this.handlerMappings = null;
        // Ensure we have at least one HandlerMapping, by registering
        // a default HandlerMapping if no other mappings are found.
        if (this.handlerMappings == null){
            // 獲取默認(rèn)HandlerMapping策略
            this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
        }
    }
    
    protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
        // 獲取需要的策略名稱(chēng)
        String key = strategyInterface.getName();
        // 從默認(rèn)策略中得到HandlerMapping的策略,在上面的圖中可以看到HandlerMapping有兩個(gè)默認(rèn)策略纺蛆,BeanNameUrlHandlerMapping和RequestMappingHandlerMapping吐葵。
        // 此處的defaultStrategies在上篇文章中提到過(guò),在靜態(tài)代碼塊中做了初始化操作桥氏。
        String value = defaultStrategies.getProperty(key);
        if (value != null) {
            String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
            List<T> strategies = new ArrayList<>(classNames.length);
            // 依次處理每個(gè)策略
            for (String className : classNames) {
                try {
                    // 通過(guò)反射將配置的類(lèi)加載到j(luò)vm中
                    Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
                    // 初始化策略對(duì)象温峭,此時(shí)會(huì)通過(guò)beanFactory來(lái)createBean,這些方法在前面分析Spring容器啟動(dòng)時(shí)已經(jīng)講過(guò)了字支。重點(diǎn)在初始化結(jié)束后調(diào)用initializeBean執(zhí)行Aware回調(diào)
                    Object strategy = createDefaultStrategy(context, clazz);
                    strategies.add((T) strategy);
                }
                catch (ClassNotFoundException ex) {
                    throw new BeanInitializationException(
                            "Could not find DispatcherServlet's default strategy class [" + className +
                            "] for interface [" + key + "]", ex);
                }
                catch (LinkageError err) {
                    throw new BeanInitializationException(
                            "Unresolvable class definition for DispatcherServlet's default strategy class [" +
                            className + "] for interface [" + key + "]", err);
                }
            }
            return strategies;
        }
        else {
            return new LinkedList<>();
        }
    }

我們已經(jīng)知道HandlerMapping默認(rèn)配置了兩個(gè)實(shí)現(xiàn)(實(shí)際還有其他實(shí)現(xiàn))凤藏,對(duì)于BeanNameUrlHandlerMapping來(lái)說(shuō)相對(duì)簡(jiǎn)單,IOC容器生成對(duì)象之后基本也就結(jié)束了初始化操作堕伪,我們來(lái)重點(diǎn)關(guān)注一下RequestMappingHandlerMapping揖庄。首先我們來(lái)看看RequestMappingHandlerMapping的類(lèi)關(guān)系圖


RequestMappingHandlerMapping.png

通過(guò)關(guān)系圖我們可以看到,RequestMappingHandlerMapping的父類(lèi)AbstractHandlerMethodMapping實(shí)現(xiàn)了InitializingBean接口欠雌,這意味著IOC容器在創(chuàng)建結(jié)束后會(huì)執(zhí)行initializeBean方法蹄梢,調(diào)用afterPropertiesSet方法。同時(shí)RequestMappingHandlerMapping重寫(xiě)了afterPropertiesSet富俄,我們來(lái)看看這個(gè)方法里面發(fā)生了什么

@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());
        // 前面只是創(chuàng)建了一個(gè)配置對(duì)象禁炒,初始化了一些配置項(xiàng)
        // 調(diào)用父類(lèi)afterPropertiesSet方法
        super.afterPropertiesSet();
    }

    /**
     * Detects handler methods at initialization.
     * @see #initHandlerMethods
     */
    @Override
    public void afterPropertiesSet() {
        initHandlerMethods();
    }

    /**
     * Scan beans in the ApplicationContext, detect and register handler methods.
     * @see #getCandidateBeanNames()
     * @see #processCandidateBean
     * @see #handlerMethodsInitialized
     */
    protected void initHandlerMethods() {
        // 這里的getCandidateBeanNames會(huì)獨(dú)去所有被容器管理的對(duì)象
        for (String beanName : getCandidateBeanNames()) {
            if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
    processCandidateBean(beanName);
            }
        }
        handlerMethodsInitialized(getHandlerMethods());
    }
    
    protected void processCandidateBean(String beanName) {
        Class<?> beanType = null;
        try {
            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);
            }
        }
        // isHandler方法會(huì)判斷當(dāng)前類(lèi)是否存在@Controller或@RequestMapping注解而咆,從而過(guò)濾非Controller對(duì)象
        if (beanType != null && isHandler(beanType)) {
            // 從此處開(kāi)始真正處理每個(gè)Controller
            detectHandlerMethods(beanName);
        }
    }

請(qǐng)注意,此時(shí)才是HandlerMapping初始化的核心部分齐苛。在AbstractHandlerMethodMapping#detectHandlerMethods方法中會(huì)對(duì)每個(gè)Controller中的所有方法進(jìn)行判斷翘盖,得到帶有@RequestMapping注解的方法并將其封裝成RequestMappingInfo桂塞。最后會(huì)將Method封裝成HandlerMethod凹蜂,同時(shí)依次在mappingLookup、urlLookup阁危、nameLookup玛痊、corsLookup和registry中注冊(cè)。下面我們來(lái)分析detectHandlerMethods方法狂打。

    /**
     * Look for handler methods in the specified handler bean.
     * @param handler either a bean name or an actual handler instance
     * @see #getMappingForMethod
     */
    protected void detectHandlerMethods(Object handler) {
        Class<?> handlerType = (handler instanceof String ?
                obtainApplicationContext().getType((String) handler) : handler.getClass());

        if (handlerType != null) {
            Class<?> userType = ClassUtils.getUserClass(handlerType);
            // 查找@RequestMapping方法并封裝成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);
                        }
                    });
            // 將Method封裝成HandlerMethod并依次注冊(cè)
            methods.forEach((method, mapping) -> {
                Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
                registerHandlerMethod(handler, invocableMethod, mapping);
            });
        }
    }
    
    public static <T> Map<Method, T> selectMethods(Class<?> targetType, final MetadataLookup<T> metadataLookup) {
        final Map<Method, T> methodMap = new LinkedHashMap<>();
        Set<Class<?>> handlerTypes = new LinkedHashSet<>();
        Class<?> specificHandlerType = null;
        for (Class<?> currentHandlerType : handlerTypes) {
            final Class<?> targetClass = (specificHandlerType != null ? specificHandlerType : currentHandlerType);
            // 判斷方法是否是橋接方法(Bridge)擂煞、組合方法(Synthetic)或者是Object中的方法,如果不是則封裝RequestMappingInfo
            ReflectionUtils.doWithMethods(currentHandlerType, method -> {
                Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass);
                T result = metadataLookup.inspect(specificMethod);
                if (result != null) {
                    Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
                    if (bridgedMethod == specificMethod || metadataLookup.inspect(bridgedMethod) == null) {
                        methodMap.put(specificMethod, result);
                    }
                }
            }, ReflectionUtils.USER_DECLARED_METHODS);
        }
        return methodMap;
    }

最終會(huì)調(diào)用到RequestMappingHandlerMapping#getMappingForMethod方法趴乡,在這個(gè)方法中會(huì)判斷是否存在@RequestMapping注解对省,不存在會(huì)跳過(guò)。同時(shí)會(huì)將方法上的@RequestMapping和類(lèi)上的@RequestMapping進(jìn)行合并(如果存在)晾捏,合并操作會(huì)將類(lèi)上的url與方法中配置的url進(jìn)行拼接蒿涎。最終返回合并后的RequestMappingInfo。
當(dāng)類(lèi)中所有的方法全部處理完之后會(huì)遍歷得到的Map惦辛,逐一將Method封裝成HandlerMethod并調(diào)用registry依次注冊(cè)到mappingLookup劳秋、urlLookup、nameLookup胖齐、corsLookup和registry中去玻淑,關(guān)鍵代碼如下

public void register(T mapping, Object handler, Method method) {
    this.readWriteLock.writeLock().lock();
    try {
        // 封裝HanderMethod
        HandlerMethod handlerMethod = createHandlerMethod(handler, method);
        assertUniqueMethodMapping(handlerMethod, mapping);
        // 以RequestMappingHandlerMapping為key,HandlerMethod為值注冊(cè)在mappingLookup中
        this.mappingLookup.put(mapping, handlerMethod);
        // 從mapping中獲取配置的patterns解析配置的Url呀伙,并以u(píng)rl為key补履,mapping為value注冊(cè)到urlLookup中
        List<String> directUrls = getDirectUrls(mapping);
        for (String url : directUrls) {
            this.urlLookup.add(url, mapping);
        }
        // 取類(lèi)名中的大寫(xiě)字母用#和方法名連接在一起作為key,handlerMethod集合作為value注冊(cè)到nameLookup中
        String name = null;
        if (getNamingStrategy() != null) {
            name = getNamingStrategy().getName(handlerMethod, mapping);
            addMappingName(name, handlerMethod);
        }
        // 初始化跨域訪(fǎng)問(wèn)配置剿另,判斷類(lèi)或者方法上是否存在CrossOrigin注解干像,如果沒(méi)有則忽略
        CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
        if (corsConfig != null) {
            this.corsLookup.put(handlerMethod, corsConfig);
        }
        // 注冊(cè)到registry中
        this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name));
    }
    finally {
        this.readWriteLock.writeLock().unlock();
    }
}

至此整個(gè)HandlerMapping就算初始化結(jié)束了,下篇文章我們繼續(xù)分析初始化HandlerAdapter的過(guò)程驰弄,這兩個(gè)過(guò)程在后續(xù)的Spring MVC接收請(qǐng)求麻汰,封裝請(qǐng)求參數(shù),調(diào)用Controller中的方法中起到了至關(guān)重要的作用戚篙。我們會(huì)著重分析這兩塊的初始化過(guò)程五鲫,之后再來(lái)分析接收到請(qǐng)求后如何封裝參數(shù),分發(fā)請(qǐng)求岔擂。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末位喂,一起剝皮案震驚了整個(gè)濱河市浪耘,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌塑崖,老刑警劉巖七冲,帶你破解...
    沈念sama閱讀 211,561評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異规婆,居然都是意外死亡澜躺,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,218評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén)抒蚜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)掘鄙,“玉大人,你說(shuō)我怎么就攤上這事嗡髓〔倌” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,162評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵饿这,是天一觀的道長(zhǎng)浊伙。 經(jīng)常有香客問(wèn)我,道長(zhǎng)长捧,這世上最難降的妖魔是什么嚣鄙? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,470評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮唆姐,結(jié)果婚禮上拗慨,老公的妹妹穿的比我還像新娘。我一直安慰自己奉芦,他們只是感情好赵抢,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,550評(píng)論 6 385
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著声功,像睡著了一般烦却。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上先巴,一...
    開(kāi)封第一講書(shū)人閱讀 49,806評(píng)論 1 290
  • 那天其爵,我揣著相機(jī)與錄音,去河邊找鬼伸蚯。 笑死摩渺,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的剂邮。 我是一名探鬼主播摇幻,決...
    沈念sama閱讀 38,951評(píng)論 3 407
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了绰姻?” 一聲冷哼從身側(cè)響起枉侧,我...
    開(kāi)封第一講書(shū)人閱讀 37,712評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎狂芋,沒(méi)想到半個(gè)月后榨馁,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,166評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡帜矾,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,510評(píng)論 2 327
  • 正文 我和宋清朗相戀三年翼虫,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片黍特。...
    茶點(diǎn)故事閱讀 38,643評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蛙讥,死狀恐怖锯蛀,靈堂內(nèi)的尸體忽然破棺而出灭衷,到底是詐尸還是另有隱情,我是刑警寧澤旁涤,帶...
    沈念sama閱讀 34,306評(píng)論 4 330
  • 正文 年R本政府宣布翔曲,位于F島的核電站,受9級(jí)特大地震影響劈愚,放射性物質(zhì)發(fā)生泄漏瞳遍。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,930評(píng)論 3 313
  • 文/蒙蒙 一菌羽、第九天 我趴在偏房一處隱蔽的房頂上張望掠械。 院中可真熱鬧,春花似錦注祖、人聲如沸猾蒂。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,745評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)肚菠。三九已至,卻和暖如春罩缴,著一層夾襖步出監(jiān)牢的瞬間蚊逢,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,983評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工箫章, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留烙荷,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,351評(píng)論 2 360
  • 正文 我出身青樓檬寂,卻偏偏與公主長(zhǎng)得像终抽,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,509評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容