進入 DispatchServlet 的“身體”

前言

springmvc 是一個標準的 MVC Web 層框架,由一個前端控制器 DispatchServlet(實際上是一個標準的繼承自 HttpServlet 的Servlet)進行總調度,Model 在 Controller 與 View 中進行數據的傳輸。如果當前請求是頁面請求的話則轉發到 VIew 中進行處理,如果是一個二進制請求的話,則直接返回 response 。

springmvc 架構.png

與其他 web 框架不一樣的是,DispatchServlet 被充分集成在了 spring IOC 容器中。因此它具有了所有 spring 所具有的特點,可以非常簡單的做到對控制器中所有 bean 的注冊與管理。并且可以在整個運行流程中及其方便的對所有已注冊了的 bean 進行訪問。每個 DispatchServlet 都有自己單獨的注冊中心,稱為 WebApplicationContext 。它可以由兩部分組成,一個單獨的 Servlet WebApplicationContext 和其所繼承的 Root WebApplicationContext。當要使用一個 bean 時,如果 Servlet WebApplicationContext 中沒有進行注冊,便去 Root WebApplicationContext 中進行查找。

TypliyRoot.png

這兩個 WebApplication 便是我們經常所在項目中所配置的兩個 applicationContext 。不過這種繼承機制也有缺陷,可能會因為某些疏忽導致 bean 的重復注冊或缺漏注冊,比如 component-scan ,如果兩者都配置了,當中間產生了交集時便會產生重復注冊,因此 spring 也提供了另外一種實現方式。

SingleRoot.png

這種方式中只有一個 Root WebApplicationContext,可以有效的避免因繼承機制而產生的問題。這種情況下的配置:

<web-app>
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/root-context.xml</param-value>
    </context-param>
    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
            <init-param>
                <param-name>contextConfigLocation</param-name>
                <param-value></param-value>
            </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
</web-app>

與 spring IOC 容器進行集成帶來的便利不僅僅只是 bean 的訪問方便,更大的優勢是擴展的方便。當我們我對其中某個 bean 進行功能的擴展時,只需要自定義一個 bean,然后在 WebApplication 中進行注冊便可以完成功能的擴展。

從 Service 到 Service

既然 DispatchServlet 是一個標準的 Servlet ,那么肯定就有 service 方法。service 方法便是前端控制器的入口,為了探究內部具體的執行流程,我們接下來新建一個項目,通過 debug 的信息來完整的探討一遍 DispatchServlet 的整體執行流程,看一下從調用 Service 到執行完 Service,這中間所經過的一些主要步驟。通過這篇文章,你可以明白以下幾點:

  • HandlerMapping 與 HandlerAdapter 的作用與執行時機
  • interceptor 的整體執行流程以及它的調用流程
  • DispatchServlet 是如何通過 request 找到它所對應的處理方法
  • Controller 中的方法是如何進行參數的注入的,以及其值的來源
  • @ResponseBody 為什么能夠返回 Json 數據,以及是如何處理的
  • Controller 中方法返回值的具體處理流程,Dispatch 是如何通過返回的 viewName 去尋找具體的 view 的

工程目錄

工程目錄.png

這是一個很普通的 web 項目目錄,只有 web 層,三個類,一個 controller 類,一個 pojo 類,一個 interceptor 。springMvc.xml 中的內容如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
        http://www.springframework.org/schema/mvc 
        http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-4.0.xsd">

    <!-- @Controller 掃描 -->
    <context:component-scan base-package="info.lmovse.explore" />

    <!-- 注解驅動: 作用:替我們自動配置最新版的注解的處理器映射器和處理器適配器 -->
    <mvc:annotation-driven />

    <!-- 初始化攔截器 -->
    <bean id="exploreInterceptor" class="info.lmovse.explore.ExploreInterceptor"/>

    <!-- 針對路徑配置攔截器 -->
    <mvc:interceptors>
        <mvc:interceptor>
            <mvc:mapping path="/**"/>
            <ref bean="exploreInterceptor"/>
        </mvc:interceptor>
    </mvc:interceptors>

    <!-- 配置視圖解析器 -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/" />
        <property name="suffix" value=".jsp" />
    </bean>
</beans>

這里注冊了 interceptor 匹配所有請求。接下來打開 controller,這里有個 explore 方法,它接收三個參數,一個 request,兩個普通的 String 參數,并且,它被標注了 @ResponseBody 注解,表明要返回的是 Json 數據。我們在 explore 方法中打個斷點,啟動項目。

explore 方法.png

方法調用棧

項目啟動后,進入首頁,這是一個表單,進行一些簡單的輸入后點提交成功進入到 explore 方法中的斷點,參數的值也成功的注入了。此時已經完成了 DispatchServlet 的 input 流程,具體是如何調用的呢?我們不可能打開 DispatchServlet 的源碼然后按照自己的理解去打斷點去調試,這樣不僅浪費時間,而且很難完整的找出所有的 input 流程。我們可以打開 debugger 的方法調用棧,在這里可以很清晰的看到 input 流程中所調用的主要方法,通過這些方法,我們便可以一步步的梳理出整個的 input 流程。

首頁輸入數據,點擊提交

首頁

進入斷點。

進入斷點.png

打開方法調用棧,可以看到這里完整的顯示出來從 request 進來到找到 explore 方法所調用的所有還未出棧方法。

方法調用棧.png

FrameWorkServlet

從方法的調用棧信息我們可以看到,前面是 Tomcat 的一些方法的調用,并且看到熟悉的 HttpServletservice 方法,而且首先調用的并不是 DispatchServlet ,是一個叫做 FrameWorkServlet 的類,它是 DispatchServlet 的父類,是 springmvc 的基礎 Servlet,在這里進行的主要工作就是對 WebApplicationContext 實例的管理,包括通過配置文件去創建一個實例。我們在 web.xml 中進行的所有配置都是在這里進行處理的。具體對 request 的處理就由它的子類 DispatchServlet 完成。

接著調用它的 doPost 方法,因為這是一個表單的 post 請求,不過其內部就是一些對 bean 的初始化處理,然后再調用子類對 doService 方法的實現來進行處理。

doPost.png
processRequest.png

doService()

到了這里,便開始真正的對 request 進行處理了,由于需要對一些執行過程的查看,我們需要實時斷點去觀察其中一些值的變化,因此需要對這些調用棧中的方法都打上斷點,一個一個去執行,打上斷點,重新啟動,輸入表單,提交,成功進入 doService 方法。

doService.png

在這個方法里,主要完成以下兩個功能

  • 保存 request 屬性的快照,使在后續處理中可以恢復原來的數據,并將一些框架的對象注入到 request 中使處理器對象或者視圖對象可以使用
// 封裝框架的一些內置對象到 request 域中
request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());
  • 注入通過 redirect 傳遞來的( RedirectAttribute) 屬性。
FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
if (inputFlashMap != null) {
    request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
}
request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);

然后調用 doDispatch 進行請求的分發

執行 doDispatch.png

doDispatch():

這個單詞的意義也表明這個方法的意義。它是請求的處理分發中心,不管是什么類型的 request 都在這里進行處理。在這里可以獲取請求所對應的 controller 方法,可以獲取它的請求映射器與請求適配器,還可以對請求進行 interceptor 的處理,它本身沒有具體的處理方法,都是通過調用本類的其它方法或者其它類的方法來進行具體的處理,然后獲取最終的處理結果。

doDispatch.png
  • 判斷當前請求是否是文件請求,如果是的話就將請求轉換為文件請求,并用文件請求解析器解析請求 。解析完后返回被轉換為文件請求的請求對象。
processedRequest = checkMultipart(request); 
    => true ? return this.multipartResolver.resolveMultipart(request) : return request;
// 標記當前請求是否被解析為文件請求,是的話處理完請求后清理文件資源 
multipartRequestParsed = (processedRequest != request);
    ...
    => true ? cleanupMultipart(processedRequest);
  • 通過配置的 HandlerMappingRequestMappingHandlerMapping) 對象來對 request 進行處理,獲取請求所對應的mappedHandler(即具體處理請求的 method 方法,實際上會最終被包裝成一個HandlerExecutionChain 對象),其原理是將路徑名與所有已注冊的路徑 map 集合進行匹配,先找出一類路徑,再將這類路徑進行排序,找到 一個最佳匹配路徑,隨后獲取該路徑對應的方法。同時也會將注冊了對應 URL 的過濾器掛載到該 chain 中,形成一條方法執行鏈。返回該 chain。(Tips: chain 中可以注入任意類型的 handler 對象,以適配其他框架的 handler 對象)。

HandlerMethod

是一種對 Method 進行封裝的對象,其內部通過傳入 一個 bean 和一個 method ,并對其進行一些封裝,使其能夠便利的獲取方法參數,方法返回值,以及方法注解等信息。

Provides convenient access to method parameters, the method return value, method annotations

HandlerExecutionChain

是一種對 handler 的鏈式封裝,可在其中掛載單個 handler 以及多個針對該 handler 的 intercepters。

Handler execution chain, consisting of handler object and any handler interceptors.

通過 hm 獲取 handler ,具體處理流程過于繁雜,不便截圖,在下面有簡單的偽代碼表示

getHandler.png

在這里可以看到當前環境有兩個映射器,三個處理器。怎么來的呢?其中 ReqMappingHandlerMappingRequestMappingHandlerAdapter 是因為我們在配置文件中配置了 <mvc:annotation-driven /> 所產生。而其他三個則是系統給的默認值。

getHandlerDebugger.png

獲取 handlerMethod 的偽代碼:

// getHandler 方法通過 request 獲取一個與請求路徑最佳匹配的 method ,并將其包裝為 HandlerMethod。
HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
    => List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
    => addMatchingMappings(directPathMatches, matches, request);
    => Collections.sort(matches, comparator);
    => Match bestMatch = matches.get(0);
    => return bestMatch.handlerMethod;
  • 獲取到 mappedHandler后,為該 handler 配置 HandlerAdapter 對象(即我們所配置的RequestMappingHandlerAdapter
  • 判斷該請求是否是支持緩存的 GET 對象,是的話通過判斷請求頭的 lastModified 屬性來判斷是否有修改,沒有的話直接返回。
handlerModified.png
  • 執行請求過濾器的 preHandler 方法,返回 TRUE 時繼續向下執行,返回 FALSE 代表請求被過濾了,這時直接返回。
doHandler.png

執行完后才開始真正的開始處理與調用 handler。

handle():

  • 調用子類的 handlerInternal 方法來進行 handler的具體細化處理,并在這里又將 HandlerExecutionChain 強轉回 HandlerMethod 對象,用于后續處理。
handlerIn.png

handleInternal()

這個方法的主要作用就是對當前請求進行一些判斷與處理,可以直接跳過,進入下一個方法。

handler.png
  • 判斷當前 request 的 method 是否被所配置的 adapter 所支持,以及當前 adapter 是否配置了 requireSession

    和該 request 是否攜帶有 session,檢查不通過拋出運行時異常

// Check whether we should support the request method.
String method = request.getMethod();
    if (this.supportedMethods != null && !this.supportedMethods.contains(method)) {
        throw new HttpRequestMethodNotSupportedException(
            method, StringUtils.toStringArray(this.supportedMethods));
    }

// Check whether a session is required.
if (this.requireSession && request.getSession(false) == null) {
    throw new HttpSessionRequiredException("Pre-existing session required but none found");
}
  • 判斷是否開啟了 session 鎖,開啟了話就在 session 鎖中執行 handlerMethod 。
// Execute invokeHandlerMethod in synchronized block if required.
if (this.synchronizeOnSession) {
    HttpSession session = request.getSession(false);
    if (session != null) {
        Object mutex = WebUtils.getSessionMutex(session);
        synchronized (mutex) {
            return invokeHandlerMethod(request, response, handlerMethod);
        }
    }
}
  • 調用 invokeHandlerMethod() 來調用處理器的方法
invokeHandlerMethod(request, response, handlerMethod);

invokeHandlerMethod()

這個方法是個很重要的方法,在這里進行了大部分后續操作必須的初始化過程。首先創建了兩個重要的對象,分別為 ServletInvocableHandlerMethodModelAndViewContainer 。其中,ServletInvocableHandlerMethod 不僅可以通過繼承獲得對方法參數進行解析的能力,還可以通過注冊一個 HandlerMethodReturnValueHandler 使其可以對返回值進行處理。 同時它也支持通過方法級別的注解 @ResponseStatus設置響應的狀態碼,如 404 ,500 等。

invokeAndHandlerMethod.png

ServletInvocableHandlerMethod => InvocableHandlerMethod => HandlerMethod

InvocableHandlerMethod

Provides a method for invoking the handler method for a given request after resolving its method argument values through registered {@link HandlerMethodArgumentResolver}s.

提供了一個可實際調用的 method,該 method 的參數通過注冊了的方法參數解析器集合遍歷解析匹配的值后后進行值的注入。同時我們自己進行配置的 converter 最終也會被注冊進方法參數解析器集合中

ServletInvocableHandlerMethod

Extends InvocableHandlerMethod with the ability to handle return values through a registered {@link HandlerMethodReturnValueHandler}s and also supports setting the response status based on a method-level
@ResponseStatus annotation.

繼承自InvocableHandlerMethod ,并通過注冊一系列的 HandlerMethodReturnValueHandler 使其有能力處理 handler 的返回值,支持通過方法級別的注解 @ResponseStatus 注解來設置響應的狀態碼。`

在這里,我們可以很清楚的看到這個方法是如何做到對 handler 的參數注入及返回值的解析處理了。它是通過 ServletInvocableHandlerMethod類中注冊的 argumentResolversreturnValueHandlers 這兩個解析器集合來解析參數及其方法執行完后的返回值的。

argumentResolversreturnValueHandlers 它們兩個所屬類的父接口分別為 HandlerMethodArgumentResolverHandlerMethodReturnValueHandler 。這是 3.1 版本新增的兩個接口,與 RequestMappingHandlerAdapter 一起出現的,用于替代之前的 AnnotionMethodHandlerAdatper,那么在 3.1 之前是怎么處理的?else if else if else if else if .................. 大量的 else if 用來判斷參數類型,導致 AnnotionMethodHandlerAdatper 類及其臃腫。為了解決這種情況,從 spring 3.1 開始,引入了 HandlerMethodArgumentResolverHandlerMethodReturnValueHandler 這兩個策略接口來分開處理,這種解決方式便是用的設計模式中的一種 —— 策略模式。

ModelAndViewContainer 這個類的可從字面意思簡單的理解為 ModelAndView 對象的容器對象。它包含一些在創建 ModeAndView 對象時的一些描述信息以及 Model 實例。在這里我們可以看到它注入了 request 中的 inputFlashMap 屬性,假如這個請求是通過 redirect 過來的話,且用了 RedirectAttribute 類來傳遞值,那么在這里就可以將其值通過 initModel 方法注入到當前的 ModelAttribute 中,然后在方法中可以通過 ModelAndAttribute 參數獲取值(能且只能通過 ModelAttribute 類獲取,因為注入的就是 ModelAttribute 類)。

最后調用 invokeAndHandle 方法進行處理,這里的 invokeAndHandler 方法屬于其父類 InvocableHandlerMethod 中的方法

doInvokeHandler.png

invokeAndHandle()

調用 invokeForRequest 方法繼續進行處理并返回 returnValue,這里并不是方法的終點,因此在執行完后還會執行到這個地方,我們繼續在 setResponseStatus 這個方法打個斷點。

invokeForRequest.png

invokeForRequest()

在這里,首先通過 getMethodArgumentValues 獲取 handler 的參數值集合。隨后,遍歷參數對象,找到與每個參數所匹配的 HandlerMethodArgumentResolver 來解析該參數。值得注意的是,遍歷過程中如果找到了合適的解析器,但是沒有成功注入值時會拋出運行時異常。一個典型的例子便是 @RequestParam 注解,如果你加了這個注解,那么參數名必須與表單名一致,否則會報錯。但如果你不加,不一致時不會報錯,而且適配 @RequstParam 的解析器同樣能夠解析沒有 @RequestParam 注解的 param 字段。

// providerArgs 在此時還是空值,為進行參數值的注入而初始化的一個參數數組
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
    =>
private Object[] getMethodArgumentValues(NativeWebRequest request, ModelAndViewContainer mavContainer,
            Object... providedArgs) throws Exception {
     
        // getMethodParameters 這個方法是 HandlerMethod 中的方法,在 handlermethod 中對參數進行處理,并將其封           裝成一個 MethodParameter 數組返回。
        MethodParameter[] parameters = getMethodParameters();
        Object[] args = new Object[parameters.length];
        for (int i = 0; i < parameters.length; i++) {
            MethodParameter parameter = parameters[i];
            // 該行代碼只為 debug 時使用,無具體意義,可忽略。
            parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
            GenericTypeResolver.resolveParameterType(parameter, getBean().getClass());
            args[i] = resolveProvidedArgument(parameter, providedArgs);
            if (args[i] != null) {
                continue;
            }
            if (this.argumentResolvers.supportsParameter(parameter)) {
                try {
                    args[i] = this.argumentResolvers.resolveArgument(
                            parameter, mavContainer, request, this.dataBinderFactory);
                    continue;
                }
                catch (Exception ex) {
                    throw ex;
                }
            }
            if (args[i] == null) {
                String msg = getArgumentResolutionErrorMessage("No suitable resolver for argument", i);
                throw new IllegalStateException(msg);
            }
        }
        return args;
    }

MethodParameter

這是一個輔助類,該類對 parameter 進行了一些描述信息的封裝,例如它在參數列表中的索引位置,或者它的嵌套泛型類的索引位置。利用該輔助類,我們可以輕松的獲取 methodparamter 的一些信息。

Helper class that encapsulates the specification of a method parameter, i.e. a {@link Method} or {@link Constructor} plus a parameter index and a nested type index for a declared generic type. Useful as a specification object to pass along.

當所有的參數數組都進行了值的注入后,便執行 doInvoke() 方法。

doInvoke()

這里只有一行代碼,通過 getBean 方法獲取到當初傳入的 handler(即 controller 中具體要執行的方法),然后傳入具體的參數值數組通過反射完成了值的注入。

getBridgedMethod().invoke(getBean(), args);

explore()

這里進行具體業務的處理,為了測試,只簡單地打印出來參數值,并返回一個 User 對象。

@RequestMapping("/explore")
@ResponseBody
public User explore(String username, String password) {
   System.out.println(username);
   System.out.println(password)
   User user = new User();
   user.setUsername(username);    
   user.setPassword(password);
   return user;
}

在這里,對方法設置了 @ResponseBody 的注解,表示要對返回對象進行 Restful 處理。handler 方法運行完后,重新進入

invokeAndHandle 方法。

invokeAndHandle()

重新回到這里。這里先對 responsestatus 進行了處理,處理后再對 returnValue 進行非空判斷,里面都是一些特殊情況的判斷,可以直接跳過,直接看最后一行代碼,調用了 this.returnValueHandlers.handleReturnValue 方法。

public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
    Object returnValue = this.invokeForRequest(webRequest, mavContainer, providedArgs);
    this.setResponseStatus(webRequest);
    if(returnValue == null) {
        if(this.isRequestNotModified(webRequest) || this.hasResponseStatus() || mavContainer.isRequestHandled()) {
            mavContainer.setRequestHandled(true);
            return;
        }
    } else if(StringUtils.hasText(this.responseReason)) {
        mavContainer.setRequestHandled(true);
        return;
    }
    mavContainer.setRequestHandled(false);
    this.returnValueHandlers.handleReturnValue(returnValue, this.getReturnValueType(returnValue), mavContainer, webRequest);
    
}

這里先調用了 selectHandler 方法進行針對該 returnValuereturnValueHandler 的選取。在 selectHandler 方法中,就是一個簡單的 forEach 循環判斷迭代的 handler 是否支持該返回類型,由于我們加了 @ResponseBody 注解。因此,毫無疑問最終會選取能處理該注解類型以及返回類型的 handler,這個 handler 的類就是當時注入的 ReturnValueHandler 集合中的 RequestResponseBodyMethodProcessor

public void handleReturnValue(Object returnValue, MethodParameter returnType,
            ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
        HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
        Assert.notNull(handler, "Unknown return value type [" + returnType.getParameterType().getName() + "]");
        handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
    }

    private HandlerMethodReturnValueHandler selectHandler(Object value, MethodParameter returnType) {
        boolean isAsyncValue = isAsyncReturnValue(value, returnType);
        for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
            if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) {
                continue;
            }
            if (handler.supportsReturnType(returnType)) {
                return handler;
            }
        }
        return null;
    }

我們點進 RequestResponseBodyMethodProcessor 中看一下,可以很清楚的看到該類就是通過獲取方法的 @ResponseBody 注解來判斷是否支持 returnType 的,同時我們通過類名以及另外一個構造函數可以看到,當我們在方法參數中使用 @RequestBody 注解時,也是使用這個 handler 進行處理的。

public boolean supportsParameter(MethodParameter parameter) {
        return parameter.hasParameterAnnotation(RequestBody.class);
}

public boolean supportsReturnType(MethodParameter returnType) {
        return AnnotationUtils.findAnnotation(returnType.getContainingClass(), ResponseBody.class) !=                   null || returnType.getMethodAnnotation(ResponseBody.class) != null;
 }

那么它是怎么處理的?如下,先將當前 request 標記為已處理狀態,然后通過一個叫做 writeWithMessageConverters 的方法進行處理,該方法并不是它本身的方法,而是從父類 AbstractMessageConverterMethodProcessor 中繼承而來的,因此我們要點進父類看具體的處理流程。

public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
        mavContainer.setRequestHandled(true);
        this.writeWithMessageConverters(returnValue, returnType, webRequest);
}

AbstractMessageConverterMethodProcessor 中,首先通過 writeWithMessageConverters 方法生成 ServletServerHttpRequestServletServerHttpResponse 對象,這兩個對象從名字就能看得出來是繼承或者實現自哪個兩個類了。繼續跟著 this.writeWithMessageConverters(...)

protected <T> void writeWithMessageConverters(T returnValue, MethodParameter returnType, NativeWebRequest webRequest) throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
        ServletServerHttpRequest inputMessage = this.createInputMessage(webRequest);
        ServletServerHttpResponse outputMessage = this.createOutputMessage(webRequest);
        this.writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}

接下來是個很長的方法,有將近 100 來行代碼,但其要做的事情就兩件:

  • 找到用戶所配置的 response contentType,并與請求所支持的 response contentType 進行對比,找出兩者都支持的 contentType 作為返回的類型。
  • 選擇合適的 HttpMessagerConverter 對象來對返回值進行 json 化,并將其寫入 responseBody 中。當在 springmvc 的配置文件中配了 <mvc: annotion-driven> 時,這個 converter 默認就是 AbstractJackson2HttpMessageConverter
// 獲取用戶所配置的 producibleMediaTypes 與 Request 所支持的 requestedMediaTypes,接下來便是遍歷判斷選擇了 
List<MediaType> requestedMediaTypes = this.getAcceptableMediaTypes(servletRequest);
List<MediaType> producibleMediaTypes = this.getProducibleMediaTypes(servletRequest, returnValueClass, returnValueType);

// 取出 HttpMessageConverter 集合中的第一個迭代對象,進行一些合法性檢查后便將 returnValue、     selectedMediaType、outputMessage 傳入 write() 中進行寫入。
HttpMessageConverter<?> messageConverter = (HttpMessageConverter)var13.next();
   if(messageConverter instanceof GenericHttpMessageConverter) {
        if(((GenericHttpMessageConverter)messageConverter).canWrite(returnValueType, returnValueClass, selectedMediaType)) {
                         returnValue = this.getAdvice().beforeBodyWrite(returnValue, returnType, selectedMediaType, messageConverter.getClass(), inputMessage, outputMessage);
                         if(returnValue != null) {
                                this.addContentDispositionHeader(inputMessage, outputMessage);
                                ((GenericHttpMessageConverter)messageConverter).write(returnValue, returnValueType, selectedMediaType, outputMessage);
               
                                }

                                return;
                     }
   }

有關 HttpMessageConverter 的相關知識或者寫入的具體流程可以看這篇博客。執行到這里,主要的步驟便執行完了,接下來便是一下細節的處理,return 后,直接返回到 invokeHandlerMethod 方法。

invokeHandlerMethod

invocableMethod.invokeAndHandle(webRequest, mavContainer, new Object[0]);
return asyncManager.isConcurrentHandlingStarted()?null:this.getModelAndView(mavContainer, modelFactory, webRequest);

在這里,直接返回的便是 ModelAndView 對象,進入 getModelAndView 方法,我們可以看到這樣一段代碼:

ModelMap model = mavContainer.getModel();
ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model);
if(!mavContainer.isViewReference()) {
      mav.setView((View)mavContainer.getView());
}
if(model instanceof RedirectAttributes) {
      Map<String, ?> flashAttributes = ((RedirectAttributes)model).getFlashAttributes();
      HttpServletRequest request = (HttpServletRequest)webRequest.getNativeRequest(HttpServletRequest.class);
                RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
 }
 return mav;

如先前所說,ModelAndViewContainer 就是 ModelAndView 的容器,通過它來生成具體的 ModelAndView 。 這里先判斷是否包含有 view ,有 view 的話設置 view,然后判斷是否有 RedirectAttributes ,有的話通過 mavContainer 獲取 flashAttributes ,并將其放入到 requestoutputFlashMap 中。

這里也執行完后,我們便又重新回到 doDispatch 方法了。

doDispatch()

處理完 handler 后,接下來便是調用請求攔截器的 postHandle 方法,進行后置攔截器的攔截,然后是處理 result 。我們知道,攔截器有三個方法,最后一個 afterCompetition 方法是要在視圖返回后調用,那么在 processDispatchResult 方法中肯定有返回視圖與調用requestafterCompetition 兩個方法。點進里面查看。

// Actually invoke the handler.
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                if (asyncManager.isConcurrentHandlingStarted()) {
                    return;
                }
                applyDefaultViewName(processedRequest, mv);
                mappedHandler.applyPostHandle(processedRequest, response, mv);
            }
            catch (Exception ex) {
                dispatchException = ex;
            }
        processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

其他的直接忽略,兩個關鍵方法:render(mv, request, responsed)mappedHandler.triggerAfterCompletion,見方法名知其義,具體實現就不進去看了。

postHandler.png

處理完后,重新回到 doService => doPost => processRequest => service

至此,整個調用過程結束。

這里只介紹了返回 Json 對象的一種情況,但也是特殊情況的一種,其他的都大同小異。

接下來是一張圖來描述一下整體的調用流程

springmvc 執行流程圖解.png
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,825評論 6 546
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,814評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,980評論 0 384
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 64,064評論 1 319
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,779評論 6 414
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,109評論 1 330
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,099評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,287評論 0 291
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,799評論 1 338
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,515評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,750評論 1 375
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,221評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,933評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,327評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,667評論 1 296
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,492評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,703評論 2 380

推薦閱讀更多精彩內容

  • 國家電網公司企業標準(Q/GDW)- 面向對象的用電信息數據交換協議 - 報批稿:20170802 前言: 排版 ...
    庭說閱讀 11,081評論 6 13
  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,836評論 18 139
  • 1. Java基礎部分 基礎部分的順序:基本語法,類相關的語法,內部類的語法,繼承相關的語法,異常的語法,線程的語...
    子非魚_t_閱讀 31,738評論 18 399
  • 轉至元數據結尾創建: 董瀟偉,最新修改于: 十二月 23, 2016 轉至元數據起始第一章:isa和Class一....
    40c0490e5268閱讀 1,757評論 0 9
  • 阿貓不是貓,是我們給小侄女的別號。 阿貓的媽媽是我們兄弟姊妹中最小的,當時父母本來是沒有準備再要孩子的,但母親意外...
    千禾隨筆閱讀 1,258評論 27 45