03-SpringMVC初始化HandlerAdapters過程分析

上篇文章中我們看到了Spring官方對DispatcherServlet的幾個特殊Bean的介紹撤防,其中第二個就是HandlerAdapter棒口,它是DispatcherServlet調(diào)用handler的關(guān)鍵,因為它會對DispatcherServlet屏蔽細(xì)節(jié)實現(xiàn)剥懒,讓DispatcherServlet只關(guān)心調(diào)用結(jié)果而不需要去關(guān)心調(diào)用細(xì)節(jié)(例如參數(shù)類型轉(zhuǎn)換及封裝等)初橘。下面我們來分析HandlerAdapter的初始化過程
我們已經(jīng)知道DispatcherServlet有一個默認(rèn)配置文件充岛,里面配置了各種策略。對于HandlerAdapter來說存在3個默認(rèn)配置夜只,他們分別是

  • HttpRequestHandlerAdapter 這個處理器是用來處理靜態(tài)資源的
  • SimpleControllerHandlerAdapter 這個處理器是用來處理在實現(xiàn)了Controller接口或者AbstractController等Spring的默認(rèn)實現(xiàn)類的子類
  • RequestMappingHandlerAdapter 這個處理器用來處理@RequestMapping注解的處理器蒜魄,也是最常用,用的最多的一個旅挤,我們今天會著重分析這個
    DispatcherServlet在初始化幾個策略時遇到使用默認(rèn)策略均會調(diào)用createDefaultStrategy方法粘茄,最終通過IOC容器創(chuàng)建對象并執(zhí)行回調(diào)秕脓。通過RequestMappingHandlerAdapter的類關(guān)系圖我們可以看出RequestMappingHandlerAdapter同樣實現(xiàn)了InitializingBean接口,因此我們關(guān)注的重點一個是在實例化階段芙贫,一個是在回調(diào)InitializingBean接口方法階段诵肛。
    首先我們來看實例化階段
// 祖先類中會設(shè)置allowHeader為除了Trace外所有的HttpMethod但并未設(shè)置supportedMethods
public WebContentGenerator(boolean restrictDefaultSupportedMethods) {
        if (restrictDefaultSupportedMethods) {
            this.supportedMethods = new LinkedHashSet<>(4);
            this.supportedMethods.add(METHOD_GET);
            this.supportedMethods.add(METHOD_HEAD);
            this.supportedMethods.add(METHOD_POST);
        }
        initAllowHeader();
}

private void initAllowHeader() {
        Collection<String> allowedMethods;
        // 這里配置allowedMethods
        if (this.supportedMethods == null) {
            allowedMethods = new ArrayList<>(HttpMethod.values().length - 1);
            for (HttpMethod method : HttpMethod.values()) {
                if (method != HttpMethod.TRACE) {
                    allowedMethods.add(method.name());
                }
            }
        }
        else if (this.supportedMethods.contains(HttpMethod.OPTIONS.name())) {
            allowedMethods = this.supportedMethods;
        }
        else {
            allowedMethods = new ArrayList<>(this.supportedMethods);
            allowedMethods.add(HttpMethod.OPTIONS.name());

        }
        this.allowHeader = StringUtils.collectionToCommaDelimitedString(allowedMethods);
    }

// 在自身的構(gòu)造器中添加了StringHttpMessageConverter、ByteArrayHttpMessageConverter蓄诽、
// SourceHttpMessageConverter媒吗、AllEncompassingFormHttpMessageConverter
public RequestMappingHandlerAdapter() {
        StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter();
        stringHttpMessageConverter.setWriteAcceptCharset(false);  // see SPR-7316

        this.messageConverters = new ArrayList<>(4);
        this.messageConverters.add(new ByteArrayHttpMessageConverter());
        this.messageConverters.add(stringHttpMessageConverter);
        try {
            this.messageConverters.add(new SourceHttpMessageConverter<>());
        }
        catch (Error err) {
            // Ignore when no TransformerFactory implementation is available
        }
        this.messageConverters.add(new AllEncompassingFormHttpMessageConverter());
    }

上述代碼中初始化了4種消息轉(zhuǎn)換器闸英,針對不同的content-type,設(shè)置不同的消息轉(zhuǎn)換器

  • StringHttpMessageConverter 支持text/plain或所有/的請求類型
  • ByteArrayHttpMessageConverter 支持application/octet-stream或所有/的請求類型
  • SourceHttpMessageConverter 支持text/xml出吹、application/xml捶牢、application/*-xml這三種類型的請求
  • AllEncompassingFormHttpMessageConverter 支持application/x-www-form-urlencoded的讀寫(file upload)和multipart/form-data格式的寫操作(RestTemplate或WebClient中發(fā)送數(shù)據(jù))巍耗。同時會根據(jù)classpath是否包含class文件來決定是否添加對xml和json的處理。
    /**
     * jaxb2Present = ClassUtils.isPresent("javax.xml.bind.Binder", classLoader);
     * jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader) &&
                        ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader);
     * jackson2XmlPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", classLoader);
     * jackson2SmilePresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.smile.SmileFactory", classLoader);
     * gsonPresent = ClassUtils.isPresent("com.google.gson.Gson", classLoader);
     * jsonbPresent = ClassUtils.isPresent("javax.json.bind.Jsonb", classLoader);
     */
    public AllEncompassingFormHttpMessageConverter() {
        try {
            addPartConverter(new SourceHttpMessageConverter<>());
        }
        catch (Error err) {
            // Ignore when no TransformerFactory implementation is available
        }

        if (jaxb2Present && !jackson2XmlPresent) {
            addPartConverter(new Jaxb2RootElementHttpMessageConverter());
        }

        if (jackson2Present) {
            addPartConverter(new MappingJackson2HttpMessageConverter());
        }
        else if (gsonPresent) {
            addPartConverter(new GsonHttpMessageConverter());
        }
        else if (jsonbPresent) {
            addPartConverter(new JsonbHttpMessageConverter());
        }

        if (jackson2XmlPresent) {
            addPartConverter(new MappingJackson2XmlHttpMessageConverter());
        }

        if (jackson2SmilePresent) {
            addPartConverter(new MappingJackson2SmileHttpMessageConverter());
        }
    }

實例化階段主要就做了這些操作,后面在分析DispatcherServlet處理過程時詳細(xì)分析各轉(zhuǎn)換器的作用亲族。
下面我們再來關(guān)注初始化后的回調(diào),開始之前我們先來看兩張圖


RequestMethod.png
ReturnValue.png

以上兩張圖來自Spring官方文檔票腰,介紹了SpringMVC的Controller中支持的請求參數(shù)類型和返回值類型。其中我們眼熟的@RequestParam测柠、@PathVariable、@RequestBody谒主、Map赃阀、Model擎颖、ServletRequest搂捧、ServletResponse懂缕、@ResponseBody、ModelAndView等等聋丝。下面就會針對上述的類型注冊相關(guān)解析器或處理器工碾。

    @Override
    public void afterPropertiesSet() {
        // Do this first, it may add ResponseBody advice beans
        // 在這個方法內(nèi)會獲取當(dāng)前IOC容器中所有注有@ControllerAdvice的Bean,將他們包裝成ControllerAdviceBean况木。
        // 并將他們分條件緩存端圈。條件包括注有@InitBinder的方法,注有@ModelAttribute但沒有@RequestMapping的方法矗晃。
        // 還會判斷ControllerAdviceBean上是否注有RequestBodyAdvice或ResponseBodyAdvice宴倍,若存在也會緩存
        initControllerAdviceCache();
        
        // 這里會初始化參數(shù)解析器(包括內(nèi)建解析器和通過setCustomArgumentResolvers方法自定義的解析器)
        if (this.argumentResolvers == null) {
            List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
            this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
        }
        // 這里會初始化一系列解析@InitBinder的解析器
        if (this.initBinderArgumentResolvers == null) {
            List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();
            this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
        }
        // 這里會初始化返回值處理器()包括內(nèi)建處理器和通過setReturnValueHandlers自定義的處理器)
        if (this.returnValueHandlers == null) {
            List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
            this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
        }
    }

至此初始化過程基本結(jié)束俗他,后面的異常處理、視圖解析兆衅、本地化嗜浮、樣式、文件上傳和請求轉(zhuǎn)發(fā)處理我們以后在分析畏铆,下一篇我們會開始分析DispatcherServlet的處理過程吉殃。
在查看官方文檔的時候發(fā)現(xiàn)Spring現(xiàn)在推薦使用MVC Config作為最佳入口楷怒,因為它申明了必須的Special Bean同時提供要給高等級的自定義配置回調(diào)API鸠删。


WebMVCConfig.png

只有無法找到MVC Config時才會使用DispatcherServlet.properties進行默認(rèn)配置冶共。后面有機會我們會重新分析MVC Config方式下的操作流程每界。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市庙楚,隨后出現(xiàn)的幾起案子趴樱,更是在濱河造成了極大的恐慌,老刑警劉巖纳账,帶你破解...
    沈念sama閱讀 221,406評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件疏虫,死亡現(xiàn)場離奇詭異啤呼,居然都是意外死亡,警方通過查閱死者的電腦和手機官扣,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,395評論 3 398
  • 文/潘曉璐 我一進店門惕蹄,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人恋昼,你說我怎么就攤上這事赶促∨副酰” “怎么了嗦哆?”我有些...
    開封第一講書人閱讀 167,815評論 0 360
  • 文/不壞的土叔 我叫張陵老速,是天一觀的道長橘券。 經(jīng)常有香客問我卿吐,道長,這世上最難降的妖魔是什么嗡官? 我笑而不...
    開封第一講書人閱讀 59,537評論 1 296
  • 正文 為了忘掉前任衍腥,我火速辦了婚禮,結(jié)果婚禮上竹捉,老公的妹妹穿的比我還像新娘。我一直安慰自己块差,他們只是感情好乖仇,可當(dāng)我...
    茶點故事閱讀 68,536評論 6 397
  • 文/花漫 我一把揭開白布乃沙。 她就那樣靜靜地躺著,像睡著了一般训裆。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上边琉,一...
    開封第一講書人閱讀 52,184評論 1 308
  • 那天变姨,我揣著相機與錄音厌丑,去河邊找鬼渔呵。 笑死砍鸠,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的录豺。 我是一名探鬼主播饭弓,決...
    沈念sama閱讀 40,776評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼兢哭!你這毒婦竟也來了夫嗓?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,668評論 0 276
  • 序言:老撾萬榮一對情侶失蹤矩父,失蹤者是張志新(化名)和其女友劉穎排霉,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體球订,經(jīng)...
    沈念sama閱讀 46,212評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡瑰钮,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,299評論 3 340
  • 正文 我和宋清朗相戀三年浪谴,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片篇恒。...
    茶點故事閱讀 40,438評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡凶杖,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出蝗茁,到底是詐尸還是另有隱情,我是刑警寧澤哮翘,帶...
    沈念sama閱讀 36,128評論 5 349
  • 正文 年R本政府宣布饭寺,位于F島的核電站叫挟,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏员凝。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,807評論 3 333
  • 文/蒙蒙 一健霹、第九天 我趴在偏房一處隱蔽的房頂上張望糖埋。 院中可真熱鬧,春花似錦瞳别、人聲如沸杭攻。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,279評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽痪宰。三九已至,卻和暖如春衣撬,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背具练。 一陣腳步聲響...
    開封第一講書人閱讀 33,395評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留岂丘,地道東北人。 一個月前我還...
    沈念sama閱讀 48,827評論 3 376
  • 正文 我出身青樓奥帘,卻偏偏與公主長得像寨蹋,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子已旧,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,446評論 2 359

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