Spring MVC URL映射 學(xué)習(xí)(上)

spring中的controller設(shè)置好URL之后,服務(wù)啟動(dòng)后spring會(huì)管理好所有的URL映射,在接收到新的http請(qǐng)求時(shí)碉纺,按照URL的映射規(guī)則進(jìn)行對(duì)應(yīng)的處理钝荡,現(xiàn)在就來(lái)具體學(xué)習(xí)下spring是如何管理URL的。

spring會(huì)獲取項(xiàng)目中所有的URL配置的屬性滨溉,然后存儲(chǔ)到對(duì)于的容器中什湘。

如圖是開始處理URL的方法調(diào)用圖,從dispatchservlet的initHandlerMappings開始晦攒,然后在getDefaultStrategies函數(shù)中從DispatcherServlet.properties配置中獲取到類的名稱是org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping闽撤,也就是BeanNameUrlHandlerMapping和DefaultAnnotationHandlerMapping兩個(gè)URL映射管理類。后續(xù)主要處理是圍繞這兩個(gè)類展開的脯颜。

image

這兩個(gè)類都會(huì)進(jìn)行下面的操作的哟旗,只是會(huì)分別介紹兩個(gè)類各自不一樣的情況。

AbstractDetectingUrlHandlerMapping類的detectHandlers方法

    protected void detectHandlers() throws BeansException {
        if (logger.isDebugEnabled()) {
            logger.debug("Looking for URL mappings in application context: " + getApplicationContext());
        }
        String[] beanNames = (this.detectHandlersInAncestorContexts ?
                BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
                getApplicationContext().getBeanNamesForType(Object.class));
        // 獲取所有的bean信息
        for (String beanName : beanNames) {
            String[] urls = determineUrlsForHandler(beanName);
            // 獲取bean對(duì)應(yīng)的類的包含的URL信息
            if (!ObjectUtils.isEmpty(urls)) {
                // URL paths found: Let's consider it a handler.
                registerHandler(urls, beanName);
            }
            else {
                if (logger.isDebugEnabled()) {
                    logger.debug("Rejected bean name '" + beanName + "': no URL paths identified");
                }
            }
        }
    }

determineUrlsForHandler 方法

BeanNameUrlHandlerMapping 類

protected String[] determineUrlsForHandler(String beanName) {
    List<String> urls = new ArrayList<String>();
    // beanName是以`/`開始的
    if (beanName.startsWith("/")) {
        urls.add(beanName);
    }
    String[] aliases = getApplicationContext().getAliases(beanName);
    // 獲取bean的alias信息
    for (String alias : aliases) {
        if (alias.startsWith("/")) {
            urls.add(alias);
        }
    }
    // 返回URL對(duì)應(yīng)的list信息
    return StringUtils.toStringArray(urls);
}

所以也并沒(méi)有進(jìn)行什么操作栋操,只是獲取了bean的name是**開頭的類闸餐,然后返回

DefaultAnnotationHandlerMapping 類

protected String[] determineUrlsForHandler(String beanName) {
    ApplicationContext context = getApplicationContext();
    Class<?> handlerType = context.getType(beanName);
    RequestMapping mapping = context.findAnnotationOnBean(beanName, RequestMapping.class);
    // 查找該bean信息是否加上了RequestMapping注解信息
    if (mapping != null) {
        // 存在注解信息(這里需要注意一下,存在類注解和函數(shù)注解的URL拼接的情況)
        this.cachedMappings.put(handlerType, mapping);
        Set<String> urls = new LinkedHashSet<String>();
        String[] typeLevelPatterns = mapping.value();
        if (typeLevelPatterns.length > 0) {
           // 如果類的注解包含了有效URL配置信息
            String[] methodLevelPatterns = determineUrlsForHandlerMethods(handlerType, true);
            // 獲取該類所有的方法的URL配置信息
            for (String typeLevelPattern : typeLevelPatterns) {
                if (!typeLevelPattern.startsWith("/")) {
                   // 如果根URL不是/ 開頭的矾芙,則加上/ 表示路徑信息
                    typeLevelPattern = "/" + typeLevelPattern;
                }
                boolean hasEmptyMethodLevelMappings = false;
                // 先設(shè)定方法中無(wú)有效URL路徑為false
                for (String methodLevelPattern : methodLevelPatterns) {
                    if (methodLevelPattern == null) {
                        hasEmptyMethodLevelMappings = true;
                        // 存在空的URL绎巨,也就是下面方法獲取URL信息說(shuō)的無(wú)有效URL
                    }
                    else {
                        String combinedPattern = getPathMatcher().combine(typeLevelPattern, methodLevelPattern);
                        addUrlsForPath(urls, combinedPattern);
                        // 組合類URL屬性和函數(shù)URL屬性,添加到集合中去
                    }
                }
                if (hasEmptyMethodLevelMappings ||
                        org.springframework.web.servlet.mvc.Controller.class.isAssignableFrom(handlerType)) {
                        // 如果是只有類URL或者類是controller類蠕啄,則添加類信息
                    addUrlsForPath(urls, typeLevelPattern);
                }
            }
            return StringUtils.toStringArray(urls);
        }
        else {
            // 類上沒(méi)有URL注解信息场勤,那就獲取方法里面的所有URL信息
            return determineUrlsForHandlerMethods(handlerType, false);
        }
    }
    else if (AnnotationUtils.findAnnotation(handlerType, Controller.class) != null) {
        // 如果方法沒(méi)有注解信息戈锻,但是是實(shí)現(xiàn)了Controller類,也可以掃描處理其函數(shù)
        return determineUrlsForHandlerMethods(handlerType, false);
    }
    else {
       // 既沒(méi)有注解信息和媳,類也不是實(shí)現(xiàn)自Controller,則該bean不存在URL注解信息格遭,返回null
        return null;
    }
}


// 獲取handleType所有的方法包含的URL配置信息
protected String[] determineUrlsForHandlerMethods(Class<?> handlerType, final boolean hasTypeLevelMapping) {
    String[] subclassResult = determineUrlsForHandlerMethods(handlerType);
    // 子類結(jié)果,如果有留瞳,則直接返回
    if (subclassResult != null) {
        return subclassResult;
    }

    final Set<String> urls = new LinkedHashSet<String>();
    Set<Class<?>> handlerTypes = new LinkedHashSet<Class<?>>();
    handlerTypes.add(handlerType);
    handlerTypes.addAll(Arrays.asList(handlerType.getInterfaces()));
    // 存儲(chǔ)著該類的信息和該類的接口信息
    for (Class<?> currentHandlerType : handlerTypes) {
       // doWithMethods會(huì)反射獲取類的所有方法信息拒迅,組合成一個(gè)list,遍歷調(diào)用doWith
        ReflectionUtils.doWithMethods(currentHandlerType, new ReflectionUtils.MethodCallback() {
            @Override
            public void doWith(Method method) {
               // 循環(huán)調(diào)用該方法她倘,處理函數(shù)
                RequestMapping mapping = AnnotationUtils.findAnnotation(method, RequestMapping.class);
                // 獲取方法包含的RequestMapping信息
                if (mapping != null) {
                    String[] mappedPatterns = mapping.value();
                    // 獲取URL數(shù)據(jù)璧微,例如`/index`
                    if (mappedPatterns.length > 0) {
                        for (String mappedPattern : mappedPatterns) {
                        //hasTypeLevelMapping 值為真意味著 類包含了URL信息,否則不包含URL信息
                            if (!hasTypeLevelMapping && !mappedPattern.startsWith("/")) {
                                mappedPattern = "/" + mappedPattern;
                            }
                            addUrlsForPath(urls, mappedPattern);
                            // 添加到mappedPattern中
                            // 并且會(huì)確認(rèn)如果URL中不包含.而且不包含/硬梁,則添加url
                            // urls.add(path + ".*");
                        // urls.add(path + "/");
                        }
                    }
                    else if (hasTypeLevelMapping) {
                        // 類包含了URL屬性前硫,但是方法卻沒(méi)有,保存一個(gè)空的URL
                        // 這里就是上面函數(shù)說(shuō)的hasEmptyMethodLevelMappings為true的情況
                        urls.add(null);
                    }
                }
            }
        }, ReflectionUtils.USER_DECLARED_METHODS);
    }
    return StringUtils.toStringArray(urls);
}

registerHandler 方法

無(wú)論是BeanNameUrlHandlerMapping還是DefaultAnnotationHandlerMapping在經(jīng)過(guò)determineUrlsForHandler函數(shù)的處理之后荧止,返回的都會(huì)是個(gè)包含了URL信息的list屹电,接下來(lái)就是去注冊(cè)到各自的容器中去。

protected void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException {

    // 獲取類信息(包含了注冊(cè)該bean信息)
    if (!this.lazyInitHandlers && handler instanceof String) {
        String handlerName = (String) handler;
        if (getApplicationContext().isSingleton(handlerName)) {
            resolvedHandler = getApplicationContext().getBean(handlerName);
        }
    }

    Object mappedHandler = this.handlerMap.get(urlPath);
    // 如果該路徑已經(jīng)存在了跃巡,而且不是同一個(gè)類危号,則認(rèn)為出現(xiàn)了沖突
    // 這個(gè)在URL設(shè)置中,如果出現(xiàn)重復(fù)了素邪,就會(huì)提示該錯(cuò)誤信息了
    if (mappedHandler != null) {
        if (mappedHandler != resolvedHandler) {
            throw new IllegalStateException(
                    "Cannot map " + getHandlerDescription(handler) + " to URL path [" + urlPath +
                    "]: There is already " + getHandlerDescription(mappedHandler) + " mapped.");
        }
    }
    else {
       // 如果是第一次設(shè)置的時(shí)候
        if (urlPath.equals("/")) {
            if (logger.isInfoEnabled()) {
                logger.info("Root mapping to " + getHandlerDescription(handler));
            }
            // 設(shè)置rootHandler
            setRootHandler(resolvedHandler);
        }
        else if (urlPath.equals("/*")) {
            if (logger.isInfoEnabled()) {
                logger.info("Default mapping to " + getHandlerDescription(handler));
            }
            // 設(shè)置默認(rèn)(全量匹配)的defaultHandler
            setDefaultHandler(resolvedHandler);
        }
        else {
           // 往該handlerMap存入該鍵值對(duì)
            this.handlerMap.put(urlPath, resolvedHandler);
            if (logger.isInfoEnabled()) {
                logger.info("Mapped URL path [" + urlPath + "] onto " + getHandlerDescription(handler));
            }
        }
    }
}

總結(jié)

以上就完成了URL信息保存到handlerMapping中了

rootHandler重置

如下圖所示外莲,先后在RootV1Controll,RootV2Controll都設(shè)置了/url屬性,然后在同隸屬于DefaultAnnotationHandlerMapping容器中被重置了

image

image
image

handlerMappings屬性信息

image
image

image
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末兔朦,一起剝皮案震驚了整個(gè)濱河市苍狰,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌烘绽,老刑警劉巖淋昭,帶你破解...
    沈念sama閱讀 206,839評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異安接,居然都是意外死亡翔忽,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門盏檐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)歇式,“玉大人,你說(shuō)我怎么就攤上這事胡野〔氖В” “怎么了?”我有些...
    開封第一講書人閱讀 153,116評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵硫豆,是天一觀的道長(zhǎng)龙巨。 經(jīng)常有香客問(wèn)我笼呆,道長(zhǎng),這世上最難降的妖魔是什么旨别? 我笑而不...
    開封第一講書人閱讀 55,371評(píng)論 1 279
  • 正文 為了忘掉前任诗赌,我火速辦了婚禮,結(jié)果婚禮上秸弛,老公的妹妹穿的比我還像新娘铭若。我一直安慰自己,他們只是感情好递览,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評(píng)論 5 374
  • 文/花漫 我一把揭開白布叼屠。 她就那樣靜靜地躺著,像睡著了一般绞铃。 火紅的嫁衣襯著肌膚如雪镜雨。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,111評(píng)論 1 285
  • 那天憎兽,我揣著相機(jī)與錄音,去河邊找鬼吵冒。 笑死纯命,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的痹栖。 我是一名探鬼主播亿汞,決...
    沈念sama閱讀 38,416評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼揪阿!你這毒婦竟也來(lái)了疗我?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,053評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤南捂,失蹤者是張志新(化名)和其女友劉穎吴裤,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體溺健,經(jīng)...
    沈念sama閱讀 43,558評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡麦牺,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了鞭缭。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片剖膳。...
    茶點(diǎn)故事閱讀 38,117評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖岭辣,靈堂內(nèi)的尸體忽然破棺而出吱晒,到底是詐尸還是另有隱情,我是刑警寧澤沦童,帶...
    沈念sama閱讀 33,756評(píng)論 4 324
  • 正文 年R本政府宣布仑濒,位于F島的核電站叹话,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏躏精。R本人自食惡果不足惜渣刷,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望矗烛。 院中可真熱鬧辅柴,春花似錦、人聲如沸瞭吃。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)歪架。三九已至股冗,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間和蚪,已是汗流浹背止状。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留攒霹,地道東北人怯疤。 一個(gè)月前我還...
    沈念sama閱讀 45,578評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像催束,于是被迫代替她去往敵國(guó)和親集峦。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評(píng)論 2 345

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

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理抠刺,服務(wù)發(fā)現(xiàn)塔淤,斷路器,智...
    卡卡羅2017閱讀 134,600評(píng)論 18 139
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,748評(píng)論 6 342
  • 要加“m”說(shuō)明是MB速妖,否則就是KB了. -Xms:初始值 -Xmx:最大值 -Xmn:最小值 java -Xms8...
    dadong0505閱讀 4,806評(píng)論 0 53
  • 從三月份找實(shí)習(xí)到現(xiàn)在高蜂,面了一些公司,掛了不少罕容,但最終還是拿到小米妨马、百度、阿里杀赢、京東烘跺、新浪、CVTE脂崔、樂(lè)視家的研發(fā)崗...
    時(shí)芥藍(lán)閱讀 42,192評(píng)論 11 349
  • (一) 想你時(shí) 我骨瘦如柴 躲在黑暗的角落里 自己抱緊自己 我的快樂(lè) 風(fēng)干在回憶里 我的憂傷 折疊在夕陽(yáng)的余暉里 ...
    雪人童話閱讀 175評(píng)論 4 7