Spring MVC 源碼分析之 請求參數(shù)解析

一崇棠、前言

在前面幾篇文章分析了請求轉(zhuǎn)發(fā)、Controller查找及攔截器的加載等信息诽嘉,那么當(dāng)帶有參數(shù)的請求發(fā)送到服務(wù)端侠姑,SpringMVC又是怎樣把請求參數(shù)创橄,分析轉(zhuǎn)換后傳入到對應(yīng)的方法中的呢?本篇文章主要分析請求參數(shù)的解析莽红、類型轉(zhuǎn)換及數(shù)據(jù)的綁定妥畏。

二、請求執(zhí)行者適配器

再次分析DispatcherServlet 中的 doDispatch方法發(fā)現(xiàn)安吁,在獲取到 Handler后會再次跟進Handler的找到執(zhí)行此handler的適配器醉蚁。如所示:

image
image.gif

?

1、查看HandlerAdapter接口的方法列表

public interface HandlerAdapter {

    boolean supports(Object handler);

    @Nullable
    ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;

    long getLastModified(HttpServletRequest request, Object handler);

}

image.gif

說明:

此處主要關(guān)心 supports方法鬼店,此方法是返回此適配器是否支持執(zhí)行對應(yīng)handler网棍。上文分析的所有的 Controller 解析成的Handler是通過適配器 RequestMappingHandlerAdapter 處理的,接下來將分析 次實現(xiàn)了妇智。

2滥玷、適配器 RequestMappingHandlerAdapter 繼承關(guān)系

image
image.gif

?

3氏身、初始化

RequestMappingHandlerAdapter實現(xiàn)了 InitializingBean 接口,屬性Spring 接口的都知道惑畴,此接口只有一個方法 afterPropertiesSet(),是在類初始化完成后調(diào)用的方法蛋欣。那么接下來咱們看看此類中afterPropertiesSet() 方法的具體實現(xiàn)。

public void afterPropertiesSet() {
        // 初始化 加了注解 @ControllerAdvice 的類的屬性信息
        initControllerAdviceCache();

  //主要實現(xiàn)三個變量  argumentResolvers initBinderArgumentResolvers  returnValueHandlers
        if (this.argumentResolvers == null) {
            List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
            this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
        }
        if (this.initBinderArgumentResolvers == null) {
            List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();
            this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
        }
        if (this.returnValueHandlers == null) {
            List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
            this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
        }
    }
image.gif

接著再看看 initControllerAdviceCace 方法的具體實現(xiàn)

private void initControllerAdviceCache() {
        if (getApplicationContext() == null) {
            return;
        }

        // 在Spring容器中獲取所有加了注解 @ControllerAdvice 的類
        List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());

        List<Object> requestResponseBodyAdviceBeans = new ArrayList<>();

        for (ControllerAdviceBean adviceBean : adviceBeans) {
            Class<?> beanType = adviceBean.getBeanType();
            if (beanType == null) {
                throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);
            }
//在當(dāng)前類中獲取所有添加了注解 @ModelAttribute 且沒有添加 注解@RequestMapping的方法桨菜,并且添加到緩存中豁状。
            Set<Method> attrMethods = MethodIntrospector.selectMethods(beanType, MODEL_ATTRIBUTE_METHODS);
            if (!attrMethods.isEmpty()) {
                this.modelAttributeAdviceCache.put(adviceBean, attrMethods);
            }
////在當(dāng)前類中獲取所有添加了注解 @InitBinder的方法,并且添加到緩存中倒得。
            Set<Method> binderMethods = MethodIntrospector.selectMethods(beanType, INIT_BINDER_METHODS);
            if (!binderMethods.isEmpty()) {
                this.initBinderAdviceCache.put(adviceBean, binderMethods);
            }
//判斷是否實現(xiàn)了 接口 RequestBodyAdvice
            if (RequestBodyAdvice.class.isAssignableFrom(beanType) || ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
                requestResponseBodyAdviceBeans.add(adviceBean);
            }
        }

        if (!requestResponseBodyAdviceBeans.isEmpty()) {
            this.requestResponseBodyAdvice.addAll(0, requestResponseBodyAdviceBeans);
        }

    }
image.gif

4、handle 處理方法

RequestMappingHandlerAdapter類的handle方法是在其父類中實現(xiàn)的夭禽,但在父類中只是調(diào)用了模板方法 handleInternal 霞掺,handleInternal方法又有具體的子類進行實現(xiàn),那么接下來在分析一下 handleInternal 方法讹躯。

@Override
    protected ModelAndView handleInternal(HttpServletRequest request,
            HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

        ModelAndView mav;
//檢測方法給定的Request是否支持 且是否要求 session菩彬。
        checkRequest(request);

        // Execute invokeHandlerMethod in synchronized block if required.
        if (this.synchronizeOnSession) {
           // 省略 同步Session調(diào)用
        }
        else {
            // No synchronization on session demanded at all...
            //主要方法
            mav = invokeHandlerMethod(request, response, handlerMethod);
        }

        if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
            if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
                applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
            }
            else {
                prepareResponse(response);
            }
        }

        return mav;
    }
image.gif

此種的重點方法為 invokeHandlerMethod 此方法較為復(fù)雜,接下來主要分析一下此方法潮梯。

protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
            HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

        ServletWebRequest webRequest = new ServletWebRequest(request, response);
        try {
            WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
            ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);

            ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
            if (this.argumentResolvers != null) {
                invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
            }
            if (this.returnValueHandlers != null) {
                invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
            }
            invocableMethod.setDataBinderFactory(binderFactory);
            invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);

            ModelAndViewContainer mavContainer = new ModelAndViewContainer();
            mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
            modelFactory.initModel(webRequest, mavContainer, invocableMethod);
            mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);

            AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
            asyncWebRequest.setTimeout(this.asyncRequestTimeout);

            WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
            asyncManager.setTaskExecutor(this.taskExecutor);
            asyncManager.setAsyncWebRequest(asyncWebRequest);
            asyncManager.registerCallableInterceptors(this.callableInterceptors);
            asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);

            if (asyncManager.hasConcurrentResult()) {
                Object result = asyncManager.getConcurrentResult();
                mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
                asyncManager.clearConcurrentResult();
                LogFormatUtils.traceDebug(logger, traceOn -> {
                    String formatted = LogFormatUtils.formatValue(result, !traceOn);
                    return "Resume with async result [" + formatted + "]";
                });
                invocableMethod = invocableMethod.wrapConcurrentResult(result);
            }

            invocableMethod.invokeAndHandle(webRequest, mavContainer);
            if (asyncManager.isConcurrentHandlingStarted()) {
                return null;
            }

            return getModelAndView(mavContainer, modelFactory, webRequest);
        }
        finally {
            webRequest.requestCompleted();
        }
    }
image.gif

此方法的功能分析:

1)骗灶、首先使用request和response創(chuàng)建了ServletWebRequest類型的webRequest,在ArgumentResolver解析參數(shù)時使用的request就是這個webRequest秉馏,當(dāng)然如果我們的處理器需要HttpServletRequest類型的參數(shù)耙旦,ArgumentResolver會給我們設(shè)置原始的request。

2)萝究、接著對WebDataBinderFactory免都、ModelFactory、ServletInvocableHandlerMethod這三個類型的變量進行了定義和初始化帆竹,下面先分別介紹一下這三個變量绕娘。

對三個變量的解析:

  • WebDataBinderFactory 的作用從名字就可以看出是用來創(chuàng)建WebDataBinder的,WebDataBinder用于參數(shù)綁定栽连,主要功能就是實現(xiàn)參數(shù)跟String之間的類型轉(zhuǎn)換险领,ArgumentResolver在進行參數(shù)解析的過程中會用到WebDataBinder,另外ModelFactory在更新Model時也會用到它秒紧。
  • ModelFactory是用來處理Model的绢陌,主要包含兩個功能:①在處理器具體處理之前對Model進行初始化;②在處理完請求后對Model參數(shù)進行更新噩茄。
  • ServletInvocableHandlerMethod 類型非常重要下面,它繼承自HandlerMethod,并且可以直接執(zhí)行绩聘。實際請求的處理就是通過它來執(zhí)行的沥割,參數(shù)綁定耗啦、處理請求以及返回值處理都在它里邊完成。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末机杜,一起剝皮案震驚了整個濱河市帜讲,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌椒拗,老刑警劉巖似将,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異蚀苛,居然都是意外死亡在验,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進店門堵未,熙熙樓的掌柜王于貴愁眉苦臉地迎上來腋舌,“玉大人,你說我怎么就攤上這事渗蟹】榻龋” “怎么了?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵雌芽,是天一觀的道長授艰。 經(jīng)常有香客問我,道長世落,這世上最難降的妖魔是什么淮腾? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮岛心,結(jié)果婚禮上来破,老公的妹妹穿的比我還像新娘。我一直安慰自己忘古,他們只是感情好徘禁,可當(dāng)我...
    茶點故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著髓堪,像睡著了一般送朱。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上干旁,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天驶沼,我揣著相機與錄音,去河邊找鬼争群。 笑死回怜,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的换薄。 我是一名探鬼主播玉雾,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼翔试,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了复旬?” 一聲冷哼從身側(cè)響起垦缅,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎驹碍,沒想到半個月后壁涎,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡志秃,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年怔球,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片洽损。...
    茶點故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡庞溜,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出碑定,到底是詐尸還是另有隱情,我是刑警寧澤又官,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布延刘,位于F島的核電站,受9級特大地震影響六敬,放射性物質(zhì)發(fā)生泄漏碘赖。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一外构、第九天 我趴在偏房一處隱蔽的房頂上張望普泡。 院中可真熱鬧,春花似錦审编、人聲如沸撼班。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽砰嘁。三九已至,卻和暖如春勘究,著一層夾襖步出監(jiān)牢的瞬間矮湘,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工口糕, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留缅阳,地道東北人。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓景描,卻偏偏與公主長得像十办,于是被迫代替她去往敵國和親秀撇。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,901評論 2 345

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