SpringMVC源碼研讀(二) 初始化映射關(guān)系

上一篇文章主要記錄了springmvc父子容器初始化的整體脈絡(luò),許多細(xì)節(jié)問題都沒有深入剖析。springmvc能夠自動(dòng)匹配請(qǐng)求url鹅很,查找到對(duì)應(yīng)的類與方法進(jìn)行處理集漾,這一功能也是需要在初始化階段進(jìn)行一些準(zhǔn)備的。這篇文章主要對(duì)映射關(guān)系的初始化做一些研究與記錄摔敛。

(一)HandlerMapping

HandlerMapping的工作就是為每個(gè)請(qǐng)求找到一個(gè)合適的處理器handler,其實(shí)現(xiàn)機(jī)制簡(jiǎn)單來說就是維持了一個(gè)從url到請(qǐng)求處理器關(guān)系的Map結(jié)構(gòu)。
HandlerMapping接口及實(shí)現(xiàn)類如下:


HandlerMapping結(jié)構(gòu)

通過上圖我們可以看到兩個(gè)主要抽象類AbstractUrlHandlerMapping和AbstractHandlerMethodMapping悄蕾。

  • AbstractHandlerMethodMapping系列是將具體的Method作為Handler來使用的,也是我們用的最多的础浮。比如經(jīng)常使用的@RequestMapping所注釋的方法就是這種Handler帆调。
  • AbstractUrlHandlerMapping是通過url來進(jìn)行匹配的,大致原理是將url與對(duì)應(yīng)的Handler保存在一個(gè)map中豆同。

(二)RequestMappingHandlerMapping

在springmvc的配置文件中番刊,這一行配置想必大家都不陌生。

<mvc:annotation-driven/>

springmvc在解析到這行配置時(shí)诱告,會(huì)調(diào)用AnnotationDrivenBeanDefinitionParser的parse方法撵枢,其中就對(duì)RequestMappingHandlerMapping進(jìn)行了注冊(cè)民晒。


標(biāo)簽處理類

RequestMappingHandlerMapping的父類AbstractHandlerMethodMapping實(shí)現(xiàn)了InitializingBean接口,并重寫了afterPropertiesSet()方法锄禽,進(jìn)行了一些初始化操作潜必,源碼如下:

public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMapping implements InitializingBean {

    @Override
    public void afterPropertiesSet() {
        initHandlerMethods();
    }

    protected void initHandlerMethods() {
        if (logger.isDebugEnabled()) {
            logger.debug("Looking for request mappings in application context: " + getApplicationContext());
        }
      //獲取springmvc上下文的所有注冊(cè)的bean
        String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
                BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
                getApplicationContext().getBeanNamesForType(Object.class));

        for (String beanName : beanNames) {
            if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
                Class<?> beanType = null;
                try {
                    beanType = getApplicationContext().getType(beanName);
                }
                catch (Throwable ex) {
                    // An unresolvable bean type, probably from a lazy bean - let's ignore it.
                    if (logger.isDebugEnabled()) {
                        logger.debug("Could not resolve target class for bean with name '" + beanName + "'", ex);
                    }
                }
        //isHandler由子類RequestMappingHandlerMapping重寫,尋找符合擁有@Controller注解或是@RequestNMapping注解的類
                if (beanType != null && isHandler(beanType)) {
                 //主要執(zhí)行步驟沃但,檢測(cè)HandlerMethod形成映射關(guān)系
                    detectHandlerMethods(beanName);
                }
            }
        }
        handlerMethodsInitialized(getHandlerMethods());
    }

    protected void detectHandlerMethods(final Object handler) {
        Class<?> handlerType = (handler instanceof String ?
                getApplicationContext().getType((String) handler) : handler.getClass());
        final Class<?> userType = ClassUtils.getUserClass(handlerType);
       //尋找bean中方法的映射關(guān)系
        Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
                new MethodIntrospector.MetadataLookup<T>() {
                    @Override
                    public T inspect(Method method) {
                        try {
                      //抽象方法磁滚,由子類實(shí)現(xiàn)具體邏輯
                            return getMappingForMethod(method, userType);
                        }
                        catch (Throwable ex) {
                            throw new IllegalStateException("Invalid mapping on handler class [" +
                                    userType.getName() + "]: " + method, ex);
                        }
                    }
                });

        if (logger.isDebugEnabled()) {
            logger.debug(methods.size() + " request handler methods found on " + userType + ": " + methods);
        }
        //對(duì)查找到的映射關(guān)系進(jìn)行注冊(cè),保存至內(nèi)部類mappingRegistry對(duì)象中
        for (Map.Entry<Method, T> entry : methods.entrySet()) {
            registerHandlerMethod(handler, entry.getKey(), entry.getValue());
        }
    }
 }

抽象方法getMappingForMethod由子類RequestMappingHandlerMapping實(shí)現(xiàn)宵晚,將Class上的@RequestMapping和Method上的@RequestMapping組裝成RequestMappingInfo對(duì)象返回垂攘。源碼如下:

    @Override
    protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
        //解析方法上的@RequestMapping注解,封裝為RequestMappingInfo對(duì)象
        RequestMappingInfo info = createRequestMappingInfo(method);
        if (info != null) {
            //解析類上的@RequestMapping注解淤刃,封裝為RequestMappingInfo對(duì)象
            RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
            if (typeInfo != null) {
                //將類與方法的映射關(guān)系相組合
                info = typeInfo.combine(info);
            }
        }
        return info;
    }

    private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
        //獲取@RequestMapping注解信息
        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);
    }

所以RequestMappingHandlerMapping保存的映射關(guān)系晒他,實(shí)際上是鍵值對(duì)Map<RequestMappingInfo, HandlerMethod> mappingLookup。對(duì)于路徑中不存在"?"和"*"的url逸贾,還會(huì)保存匹配關(guān)系到MultiValueMap<String, RequestMappingInfo> urlLookup中陨仅,其中key為方法的directUrl。

(三)SimpleUrlHandlerMapping

上一節(jié)講的是AbstractHandlerMethodMapping實(shí)現(xiàn)類铝侵,接下來講一下AbstractUrlHandlerMapping的實(shí)現(xiàn)類SimpleUrlHandlerMapping灼伤,通常用于靜態(tài)資源映射。
在進(jìn)行springmvc的配置時(shí)咪鲜,通常我們會(huì)配置一個(gè)dispatcher servlet用于處理對(duì)應(yīng)的URL狐赡。配置如下:


springmvc配置

由于配置的攔截路徑是"/",所有路徑的請(qǐng)求都會(huì)轉(zhuǎn)由DispatcherServlet處理疟丙。當(dāng)試圖訪問靜態(tài)資源路徑時(shí)颖侄,由于沒有對(duì)應(yīng)的controller來處理請(qǐng)求,會(huì)導(dǎo)致訪問失敗隆敢。
需要在mvc配置文件中加入如下類似配置來解決靜態(tài)資源映射問題发皿。

<mvc:resources mapping="/assets/**" location="/assets/"/>

那這是怎么生效的呢?


標(biāo)簽處理

mvc:resources標(biāo)簽對(duì)應(yīng)的解析器是ResourcesBeanDefinitionParser拂蝎。在parse方法中穴墅,解析器注冊(cè)了SimpleUrlHandlerMapping,用于保存靜態(tài)資源url與對(duì)應(yīng)處理器的映射關(guān)系温自。SimpleUrlHandlerMapping的處理優(yōu)先級(jí)是最低的玄货,在其他HandlerMapping找不到映射關(guān)系時(shí)才會(huì)轉(zhuǎn)由它處理。源碼如下:

    @Override
    public BeanDefinition parse(Element element, ParserContext parserContext) {
        Object source = parserContext.extractSource(element);

        registerUrlProvider(parserContext, source);
                //注冊(cè)靜態(tài)資源處理器ResourceHttpRequestHandler
        String resourceHandlerName = registerResourceHandler(parserContext, element, source);
        if (resourceHandlerName == null) {
            return null;
        }

        Map<String, String> urlMap = new ManagedMap<String, String>();
        String resourceRequestPath = element.getAttribute("mapping");
        if (!StringUtils.hasText(resourceRequestPath)) {
            parserContext.getReaderContext().error("The 'mapping' attribute is required.", parserContext.extractSource(element));
            return null;
        }
        urlMap.put(resourceRequestPath, resourceHandlerName);

        RuntimeBeanReference pathMatcherRef = MvcNamespaceUtils.registerPathMatcher(null, parserContext, source);
        RuntimeBeanReference pathHelperRef = MvcNamespaceUtils.registerUrlPathHelper(null, parserContext, source);
                //注冊(cè)SimpleUrlHandlerMapping
        RootBeanDefinition handlerMappingDef = new RootBeanDefinition(SimpleUrlHandlerMapping.class);
        handlerMappingDef.setSource(source);
        handlerMappingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
                //key為配置文件中的mapping悼泌,value為處理器ResourceHttpRequestHandler
        handlerMappingDef.getPropertyValues().add("urlMap", urlMap);
        handlerMappingDef.getPropertyValues().add("pathMatcher", pathMatcherRef).add("urlPathHelper", pathHelperRef);

        String order = element.getAttribute("order");
                //支持自定義mapping優(yōu)先級(jí)松捉,默認(rèn)設(shè)置order為2147483646
        handlerMappingDef.getPropertyValues().add("order", StringUtils.hasText(order) ? order : Ordered.LOWEST_PRECEDENCE - 1);

        RuntimeBeanReference corsConfigurationsRef = MvcNamespaceUtils.registerCorsConfigurations(null, parserContext, source);
        handlerMappingDef.getPropertyValues().add("corsConfigurations", corsConfigurationsRef);

        String beanName = parserContext.getReaderContext().generateBeanName(handlerMappingDef);
        parserContext.getRegistry().registerBeanDefinition(beanName, handlerMappingDef);
        parserContext.registerComponent(new BeanComponentDefinition(handlerMappingDef, beanName));
        MvcNamespaceUtils.registerDefaultComponents(parserContext, source);

        return null;
    }

(四)總結(jié)

研讀了springmvc中兩個(gè)運(yùn)用比較多的HandlerMapping的初始化過程,隨后DispatcherServlet執(zhí)行initHandlerMappings方法馆里,直接從ApplicationContext容器中獲取HandlerMapping列表隘世,經(jīng)由order排序后注入可柿。


DispatcherServlet注入handlerMappings

DispatcherServlet初始化后的handlerMappings

為后續(xù)文章分析DispatcherServlet的分發(fā)與處理請(qǐng)求過程打下基礎(chǔ)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末丙者,一起剝皮案震驚了整個(gè)濱河市复斥,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌械媒,老刑警劉巖目锭,帶你破解...
    沈念sama閱讀 211,639評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異纷捞,居然都是意外死亡痢虹,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,277評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門主儡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來奖唯,“玉大人,你說我怎么就攤上這事缀辩〕袈瘢” “怎么了?”我有些...
    開封第一講書人閱讀 157,221評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵臀玄,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我畅蹂,道長(zhǎng)健无,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,474評(píng)論 1 283
  • 正文 為了忘掉前任液斜,我火速辦了婚禮累贤,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘少漆。我一直安慰自己臼膏,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,570評(píng)論 6 386
  • 文/花漫 我一把揭開白布示损。 她就那樣靜靜地躺著渗磅,像睡著了一般。 火紅的嫁衣襯著肌膚如雪检访。 梳的紋絲不亂的頭發(fā)上始鱼,一...
    開封第一講書人閱讀 49,816評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音脆贵,去河邊找鬼医清。 笑死,一個(gè)胖子當(dāng)著我的面吹牛卖氨,可吹牛的內(nèi)容都是我干的会烙。 我是一名探鬼主播负懦,決...
    沈念sama閱讀 38,957評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼柏腻!你這毒婦竟也來了密似?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,718評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤葫盼,失蹤者是張志新(化名)和其女友劉穎残腌,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體贫导,經(jīng)...
    沈念sama閱讀 44,176評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡抛猫,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,511評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了孩灯。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片闺金。...
    茶點(diǎn)故事閱讀 38,646評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖峰档,靈堂內(nèi)的尸體忽然破棺而出败匹,到底是詐尸還是另有隱情,我是刑警寧澤讥巡,帶...
    沈念sama閱讀 34,322評(píng)論 4 330
  • 正文 年R本政府宣布掀亩,位于F島的核電站,受9級(jí)特大地震影響欢顷,放射性物質(zhì)發(fā)生泄漏槽棍。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,934評(píng)論 3 313
  • 文/蒙蒙 一抬驴、第九天 我趴在偏房一處隱蔽的房頂上張望炼七。 院中可真熱鬧,春花似錦布持、人聲如沸豌拙。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,755評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)按傅。三九已至,卻和暖如春芙委,著一層夾襖步出監(jiān)牢的瞬間逞敷,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,987評(píng)論 1 266
  • 我被黑心中介騙來泰國(guó)打工灌侣, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留推捐,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,358評(píng)論 2 360
  • 正文 我出身青樓侧啼,卻偏偏與公主長(zhǎng)得像牛柒,于是被迫代替她去往敵國(guó)和親堪簿。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,514評(píng)論 2 348

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