Spring MVC 原理探祕 - 一個請求的旅行過程

1.簡介

在前面的文章中,我較爲詳細的分析了 Spring IOC 和 AOP 部分的源碼,並寫成了文章。爲了讓我的 Spring 源碼分析系列文章更爲豐富一些,所以從本篇文章開始,我將來向大家介紹一下 Spring MVC 的一些原理。在本篇文章中,你將會瞭解到 Spring MVC 處理請求的過程。同時,你也會瞭解到 Servlet 相關的知識。以及 Spring MVC 的核心 DispatcherServlet 類的源碼分析。在掌握以上內容後,相信大家會對 Spring MVC 的原理有更深的認識。

如果大家對上面介紹的知識點感興趣的話,那下面不妨和我一起來去探索 Spring MVC 的原理。Let`s Go。

 2.一個請求的旅行過程

在探索更深層次的原理之前,我們先來了解一下 Spring MVC 是怎麼處理請求的。弄懂了這個流程後,才能更好的理解具體的源碼。這裏我把 Spring MVC 處理請求的流程圖畫了出來,一起看一下吧:

如上,每一個重要的步驟上面都有編號。我先來簡單分析一下上面的流程,然後再向大家介紹圖中出現的一些組件。我們從第一步開始,首先,用戶的瀏覽器發出了一個請求,這個請求經過互聯網到達了我們的服務器。Servlet 容器首先接待了這個請求,並將該請求委託給 DispatcherServlet 進行處理。接着 DispatcherServlet 將該請求傳給了處理器映射組件 HandlerMapping,並獲取到適合該請求的攔截器和處理器。在獲取到處理器後,DispatcherServlet 還不能直接調用處理器的邏輯,需要進行對處理器進行適配。處理器適配成功後,DispatcherServlet 通過處理器適配器 HandlerAdapter 調用處理器的邏輯,並獲取返回值 ModelAndView。之後,DispatcherServlet 需要根據 ModelAndView 解析視圖。解析視圖的工作由 ViewResolver 完成,若能解析成功,ViewResolver 會返回相應的視圖對象 View。在獲取到具體的 View 對象後,最後一步要做的事情就是由 View 渲染視圖,並將渲染結果返回給用戶。

以上就是 Spring MVC 處理請求的全過程,上面的流程進行了一定的簡化,比如攔截器的執行時機就沒說。不過這並不影響大家對主過程的理解。下來來簡單介紹一下圖中出現的一些組件:

組件 說明
DispatcherServlet Spring MVC 的核心組件,是請求的入口,負責協調各個組件工作
HandlerMapping 內部維護了一些 <訪問路徑, 處理器> 映射,負責爲請求找到合適的處理器
HandlerAdapter 處理器的適配器。Spring 中的處理器的實現多變,比如用戶處理器可以實現 Controller 接口,也可以用 @RequestMapping 註解將方法作爲一個處理器等,這就導致 Spring 不止到怎麼調用用戶的處理器邏輯。所以這裏需要一個處理器適配器,由處理器適配器去調用處理器的邏輯
ViewResolver 視圖解析器的用途不難理解,用於將視圖名稱解析爲視圖對象 View。
View 視圖對象用於將模板渲染成 html 或其他類型的文件。比如 InternalResourceView 可將 jsp 渲染成 html。

從上面的流程中可以看出,Spring MVC 對各個組件的職責劃分的比較清晰。DispatcherServlet 負責協調,其他組件則各自做分內之事,互不干擾。經過這樣的職責劃分,代碼會便於維護。同時對於源碼閱讀者來說,也會很友好。可以降低理解源碼的難度,使大家能夠快速理清主邏輯。這一點值得我們學習。

 3.知其然,更要知其所以然

 3.1 追根溯源之 Servlet

本章要向大家介紹一下 Servlet,爲什麼要介紹 Servlet 呢?原因不難理解,Spring MVC 是基於 Servlet 實現的。所以要分析 Spring MVC,首先應追根溯源,弄懂 Servlet。Servlet 是 J2EE 規範之一,在遵守該規範的前提下,我們可將 Web 應用部署在 Servlet 容器下。這樣做的好處是什麼呢?我覺得可使開發者聚焦業務邏輯,而不用去關心 HTTP 協議方面的事情。比如,普通的 HTTP 請求就是一段有格式的文本,服務器需要去解析這段文本才能知道用戶請求的內容是什麼。比如我對個人網站的 80 端口抓包,然後獲取到的 HTTP 請求頭如下:

如果我們爲了寫一個 Web 應用,還要去解析 HTTP 協議相關的內容,那會增加很多工作量。有興趣的朋友可以考慮使用 Java socket 編寫實現一個 HTTP 服務器,體驗一下解析部分 HTTP 協議的過程。也可以參考我之前寫的文章 - 基於 Java NIO 實現簡單的 HTTP 服務器

如果我們寫的 Web 應用不大,不誇張的說,項目中對 HTTP 提供支持的代碼會比業務代碼還要多,這豈不是得不償失。當然,在現實中,有現成的框架可用,並不需要自己造輪子。如果我們基於 Servlet 規範實現 Web 應用的話,HTTP 協議的處理過程就不需要我們參與了。這些工作交給 Servlet 容器就行了,我們只需要關心業務邏輯怎麼實現即可。

下面,我們先來看看 Servlet 接口及其實現類結構,然後再進行更進一步的說明。

如上圖,我們接下來按照從上到下順序進行分析。先來看看最頂層的兩個接口是怎麼定義的。

 3.1.1 Servlet 與 ServletConfig

先來看看 Servlet 接口的定義,如下:

1
2
3
4
5
6
7
8
9
10
11
12
public interface Servlet {

    public void init(ServletConfig config) throws ServletException;

    public ServletConfig getServletConfig();
    
    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException;
   
    public String getServletInfo();
    
    public void destroy();
}

init 方法會在容器啓動時由容器調用,也可能會在 Servlet 第一次被使用時調用,調用時機取決 load-on-start 的配置。容器調用 init 方法時,會向其傳入一個 ServletConfig 參數。ServletConfig 是什麼呢?顧名思義,ServletConfig 是一個和 Servlet 配置相關的接口。舉個例子說明一下,我們在配置 Spring MVC 的 DispatcherServlet 時,會通過 ServletConfig 將配置文件的位置告知 DispatcherServlet。比如:

1
2
3
4
5
6
7
8
<servlet>
    <servlet-name>dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:application-web.xml</param-value>
    </init-param>
</servlet>

如上,標籤內的配置信息最終會被放入 ServletConfig 實現類對象中。DispatcherServlet 通過 ServletConfig 接口中的方法,就能獲取到 contextConfigLocation 對應的值。

Servlet 中的 service 方法用於處理請求。當然,一般情況下我們不會直接實現 Servlet 接口,通常是通過繼承 HttpServlet 抽象類編寫業務邏輯的。Servlet 中接口不多,也不難理解,這裏就不多說了。下面我們來看看 ServletConfig 接口定義,如下:

1
2
3
4
5
6
7
8
9
10
public interface ServletConfig {
    
    public String getServletName();

    public ServletContext getServletContext();

    public String getInitParameter(String name);

    public Enumeration<String> getInitParameterNames();
}

先來看看 getServletName 方法,該方法用於獲取 servlet 名稱,也就是標籤中配置的內容。getServletContext 方法用於獲取 Servlet 上下文。如果說一個 ServletConfig 對應一個 Servlet,那麼一個 ServletContext 則是對應所有的 Servlet。ServletContext 代表當前的 Web 應用,可用於記錄一些全局變量,當然它的功能不侷限於記錄變量。我們可通過標籤向 ServletContext 中配置信息,比如在配置 Spring 監聽器(ContextLoaderListener)時,就可以通過該標籤配置 contextConfigLocation。如下:

1
2
3
4
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:application.xml</param-value>
</context-param>

關於 ServletContext 就先說這麼多了,繼續介紹 ServletConfig 中的其他方法。getInitParameter 方法用於獲取標籤中配置的參數值,getInitParameterNames 則是獲取所有配置的名稱集合,這兩個方法用途都不難理解。

以上是 Servlet 與 ServletConfig 兩個接口的說明,比較簡單。說完這兩個接口,我們繼續往下看,接下來是 GenericServlet。

 3.1.2 GenericServlet

GenericServlet 實現了 Servlet 和 ServletConfig 兩個接口,爲這兩個接口中的部分方法提供了簡單的實現。比如該類實現了 Servlet 接口中的 void init(ServletConfig) 方法,並在方法體內調用了內部提供了一個無參的 init 方法,子類可覆蓋該無參 init 方法。除此之外,GenericServlet 還實現了 ServletConfig 接口中的 getInitParameter 方法,用戶可直接調用該方法獲取到配置信息。而不用先獲取 ServletConfig,然後再調用 ServletConfig 的 getInitParameter 方法獲取。下面我們來看看 GenericServlet 部分方法的源碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public abstract class GenericServlet 
    implements Servlet, ServletConfig, java.io.Serializable {

    // 省略部分代碼

    private transient ServletConfig config;
    
    public GenericServlet() { } 
    
    /** 有參 init 方法 */
    public void init(ServletConfig config) throws ServletException {
        this.config = config;
        // 調用內部定義的無參 init 方法
        this.init();
    }

    /** 無參 init 方法,子類可覆蓋該方法 */
    public void init() throws ServletException { }

    /** 未給 service 方法提供具體的實現 */
    public abstract void service(ServletRequest req, ServletResponse res) throws ServletException, IOException;

    public void destroy() { }

    /** 通過 getInitParameter 可直接從 ServletConfig 實現類中獲取配置信息 */
    public String getInitParameter(String name) {
        ServletConfig sc = getServletConfig();
        if (sc == null) {
            throw new IllegalStateException(
                lStrings.getString("err.servlet_config_not_initialized"));
        }

        return sc.getInitParameter(name);
    } 

    public ServletConfig getServletConfig() {
        return config;
    }
    
    // 省略部分代碼
}

如上,GenericServlet 代碼比較簡單,配合着我寫註釋,很容易看懂。

GenericServlet 是一個協議無關的 servlet,是一個比較原始的實現,通常我們不會直接繼承該類。一般情況下,我們都是繼承 GenericServlet 的子類 HttpServlet,該類是一個和 HTTP 協議相關的 Servlet。那下面我們來看一下這個類。

 3.1.3 HttpServlet

HttpServlet,從名字上就可看出,這個類是和 HTTP 協議相關。該類的關注點在於怎麼處理 HTTP 請求,比如其定義了 doGet 方法處理 GET 類型的請求,定義了 doPost 方法處理 POST 類型的請求等。我們若需要基於 Servlet 寫 Web 應用,應繼承該類,並覆蓋指定的方法。doGet 和 doPost 等方法並不是處理的入口方法,所以這些方法需要由其他方法調用才行。其他方法是哪個方法呢?當然是 service 方法了。下面我們看一下這個方法的實現。如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
@Override
public void service(ServletRequest req, ServletResponse res)
    throws ServletException, IOException {
    HttpServletRequest  request;
    HttpServletResponse response;
    
    if (!(req instanceof HttpServletRequest &&
            res instanceof HttpServletResponse)) {
        throw new ServletException("non-HTTP request or response");
    }

    request = (HttpServletRequest) req;
    response = (HttpServletResponse) res;

    // 調用重載方法,該重載方法接受 HttpServletRequest 和 HttpServletResponse 類型的參數
    service(request, response);
}

protected void service(HttpServletRequest req, HttpServletResponse resp)
    throws ServletException, IOException {
    String method = req.getMethod();

    // 處理 GET 請求
    if (method.equals(METHOD_GET)) {
        long lastModified = getLastModified(req);
        if (lastModified == -1) {
            // 調用 doGet 方法
            doGet(req, resp);
        } else {
            long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
            if (ifModifiedSince < lastModified) {
                maybeSetLastModified(resp, lastModified);
                doGet(req, resp);
            } else {
                resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
            }
        }

    // 處理 HEAD 請求
    } else if (method.equals(METHOD_HEAD)) {
        long lastModified = getLastModified(req);
        maybeSetLastModified(resp, lastModified);
        doHead(req, resp);

    // 處理 POST 請求
    } else if (method.equals(METHOD_POST)) {
        // 調用 doPost 方法
        doPost(req, resp);
    } else if (method.equals(METHOD_PUT)) {
        doPut(req, resp);
    } else if (method.equals(METHOD_DELETE)) {
        doDelete(req, resp);
    } else if (method.equals(METHOD_OPTIONS)) {
        doOptions(req,resp);
    } else if (method.equals(METHOD_TRACE)) {
        doTrace(req,resp);
    } else {
        String errMsg = lStrings.getString("http.method_not_implemented");
        Object[] errArgs = new Object[1];
        errArgs[0] = method;
        errMsg = MessageFormat.format(errMsg, errArgs);
        
        resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
    }
}

如上,第一個 service 方法覆蓋父類中的抽象方法,並沒什麼太多邏輯。所有的邏輯集中在第二個 service 方法中,該方法根據請求類型分發請求。我們可以根據需要覆蓋指定的處理方法。

以上所述只是 Servlet 規範中的一部分內容,這些內容是和本文相關的內容。對於 Servlet 規範中的其他內容,大家有興趣可以自己去探索。好了,關於 Servlet 方面的內容,這裏先說這麼多。

 3.2 DispatcherServlet 族譜

我在前面說到,DispatcherServlet 是 Spring MVC 的核心。所以在分析這個類的源碼前,我們有必要了解一下它的族譜,也就是繼承關係圖。如下:

如上圖,紅色框是 Servlet 中的接口和類,藍色框中則是 Spring 中的接口和類。關於 Servlet 內容前面已經說過,下面來簡單介紹一下藍色框中的接口和類,我們從最頂層的接口開始。

● Aware

在 Spring 中,Aware 類型的接口用於向 Spring “索要”一些框架中的信息。比如當某個 bean 實現了 ApplicationContextAware 接口時,Spring 在運行時會將當前的 ApplicationContext 實例通過接口方法 setApplicationContext 傳給該 bean。下面舉個例子說明,這裏我寫一個 SystemInfo API,通過該 API 返回一些系統信息。代碼如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
@RestController
@RequestMapping("/systeminfo")
public class SystemInfo implements ApplicationContextAware, EnvironmentAware {

    private ApplicationContext applicationContext;

    private Environment environment;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        System.out.println(applicationContext.getClass());
        this.applicationContext = applicationContext;
    }

    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }

    @RequestMapping("/env")
    public String environment() {
        StandardServletEnvironment sse = (StandardServletEnvironment) environment;
        Map<String, Object> envs = sse.getSystemEnvironment();
        StringBuilder sb = new StringBuilder();
        sb.append("-------------------------++ System Environment ++-------------------------\n");

        List<String> list = new ArrayList<>();
        list.addAll(envs.keySet());

        for (int i = 0; i < 5 && i < list.size(); i++) {
            String key = list.get(i);
            Object val = envs.get(key);
            sb.append(String.format("%s = %s\n", key, val.toString()));
        }

        Map<String, Object> props = sse.getSystemProperties();
        sb.append("\n-------------------------++ System Properties ++-------------------------\n");
        list.clear();
        list.addAll(props.keySet());
        for (int i = 0; i < 5 && i < list.size(); i++) {
            String key = list.get(i);
            Object val = props.get(key);
            sb.append(String.format("%s = %s\n", key, val.toString()));
        }

        return sb.toString();
    }

    @RequestMapping("/beans")
    public String listBeans() {
        ListableBeanFactory lbf = applicationContext;
        String[] beanNames = lbf.getBeanDefinitionNames();
        StringBuilder sb = new StringBuilder();
        sb.append("-------------------------++ Bean Info ++-------------------------\n");
        Arrays.stream(beanNames).forEach(beanName -> {
            Object bean = lbf.getBean(beanName);
            sb.append(String.format("beanName  = %s\n", beanName));
            sb.append(String.format("beanClass = %s\n\n", bean.getClass().toString()));
        });

        return sb.toString();
    }
}

如上,SystemInfo 分別實現了 ApplicationContextAware 和 EnvironmentAware 接口,因此它可以在運行時獲取到 ApplicationContext 和 Environment 實例。下面我們調一下接口看看結果吧:

如上,我們通過接口拿到了環境變量、配置信息以及容器中所有 bean 的數據。這說明,Spring 在運行時向 SystemInfo 中注入了 ApplicationContext 和 Environment 實例。

● EnvironmentCapable

EnvironmentCapable 僅包含一個方法定義 getEnvironment,通過該方法可以獲取到環境變量對象。我們可以將 EnvironmentCapable 和 EnvironmentAware 接口配合使用,比如下面的實例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class EnvironmentHolder implements EnvironmentCapable, EnvironmentAware {

    private Environment environment;

    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }

    @Override
    public Environment getEnvironment() {
        return environment;
    }
}

● HttpServletBean

HttpServletBean 是 HttpServlet 抽象類的簡單拓展。HttpServletBean 覆寫了父類中的無參 init 方法,並在該方法中將 ServletConfig 裏的配置信息設置到子類對象中,比如 DispatcherServlet。

● FrameworkServlet

FrameworkServlet 是 Spring Web 框架中的一個基礎類,該類會在初始化時創建一個容器。同時該類覆寫了 doGet、doPost 等方法,並將所有類型的請求委託給 doService 方法去處理。doService 是一個抽象方法,需要子類實現。

● DispatcherServlet

DispatcherServlet 主要的職責相信大家都比較清楚了,即協調各個組件工作。除此之外,DispatcherServlet 還有一個重要的事情要做,即初始化各種組件,比如 HandlerMapping、HandlerAdapter 等。

 3.3 DispatcherServlet 源碼簡析

在第二章中,我們知道了一個 HTTP 請求是怎麼樣被 DispatcherServlet 處理的。本節,我們從源碼的角度對第二章的內容進行補充說明。這裏,我們直入主題,直接分析 DispatcherServlet 中的 doDispatch 方法。這裏我把請求的處理流程圖再貼一遍,大家可以對着流程圖閱讀源碼。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    HttpServletRequest processedRequest = request;
    HandlerExecutionChain mappedHandler = null;
    boolean multipartRequestParsed = false;

    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

    try {
        ModelAndView mv = null;
        Exception dispatchException = null;

        try {
            processedRequest = checkMultipart(request);
            multipartRequestParsed = (processedRequest != request);

            // 獲取可處理當前請求的處理器 Handler,對應流程圖中的步驟②
            mappedHandler = getHandler(processedRequest);
            if (mappedHandler == null || mappedHandler.getHandler() == null) {
                noHandlerFound(processedRequest, response);
                return;
            }

            // 獲取可執行處理器邏輯的適配器 HandlerAdapter,對應步驟③
            HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

            // 處理 last-modified 消息頭
            String method = request.getMethod();
            boolean isGet = "GET".equals(method);
            if (isGet || "HEAD".equals(method)) {
                long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                if (logger.isDebugEnabled()) {
                    logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
                }
                if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                    return;
                }
            }

            // 執行攔截器 preHandle 方法
            if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                return;
            }

            // 調用處理器邏輯,對應步驟④
            mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

            if (asyncManager.isConcurrentHandlingStarted()) {
                return;
            }

            // 如果 controller 未返回 view 名稱,這裏生成默認的 view 名稱
            applyDefaultViewName(processedRequest, mv);

            // 執行攔截器 preHandle 方法
            mappedHandler.applyPostHandle(processedRequest, response, mv);
        }
        catch (Exception ex) {
            dispatchException = ex;
        }
        catch (Throwable err) {
            dispatchException = new NestedServletException("Handler dispatch failed", err);
        }
        
        // 解析並渲染視圖
        processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    }
    catch (Exception ex) {
        triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
    }
    catch (Throwable err) {
        triggerAfterCompletion(processedRequest, response, mappedHandler,
                new NestedServletException("Handler processing failed", err));
    }
    finally {
        if (asyncManager.isConcurrentHandlingStarted()) {
            // Instead of postHandle and afterCompletion
            if (mappedHandler != null) {
                mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
            }
        }
        else {
            if (multipartRequestParsed) {
                cleanupMultipart(processedRequest);
            }
        }
    }
}

private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
        HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception {

    boolean errorView = false;

    if (exception != null) {
        if (exception instanceof ModelAndViewDefiningException) {
            logger.debug("ModelAndViewDefiningException encountered", exception);
            mv = ((ModelAndViewDefiningException) exception).getModelAndView();
        }
        else {
            Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
            mv = processHandlerException(request, response, handler, exception);
            errorView = (mv != null);
        }
    }

    if (mv != null && !mv.wasCleared()) {
        // 渲染視圖
        render(mv, request, response);
        if (errorView) {
            WebUtils.clearErrorRequestAttributes(request);
        }
    }
    else {
        if (logger.isDebugEnabled()) {...
    }

    if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
        return;
    }

    if (mappedHandler != null) {
        mappedHandler.triggerAfterCompletion(request, response, null);
    }
}

protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
    Locale locale = this.localeResolver.resolveLocale(request);
    response.setLocale(locale);

    View view;
    /*
     * 若 mv 中的 view 是 String 類型,即處理器返回的是模板名稱,
     * 這裏將其解析爲具體的 View 對象
     */ 
    if (mv.isReference()) {
        // 解析視圖,對應步驟⑤
        view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request);
        if (view == null) {
            throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
                    "' in servlet with name '" + getServletName() + "'");
        }
    }
    else {
        view = mv.getView();
        if (view == null) {
            throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +
                    "View object in servlet with name '" + getServletName() + "'");
        }
    }

    if (logger.isDebugEnabled()) {...}
    try {
        if (mv.getStatus() != null) {
            response.setStatus(mv.getStatus().value());
        }
        // 渲染視圖,並將結果返回給用戶。對應步驟⑥和⑦
        view.render(mv.getModelInternal(), request, response);
    }
    catch (Exception ex) {
        if (logger.isDebugEnabled()) {...}
        throw ex;
    }
}

以上就是 doDispatch 方法的分析過程,我已經做了較爲詳細的註釋,這裏就不多說了。需要說明的是,以上只是進行了簡單分析,並沒有深入分析每個方法調用。大家若有興趣,可以自己去分析一下 doDispatch 所調用的一些方法,比如 getHandler 和 getHandlerAdapter,這兩個方法比較簡單。從我最近所分析的源碼來看,我個人覺得處理器適配器 RequestMappingHandlerAdapter 應該是 Spring MVC 中最爲複雜的一個類。該類用於對 @RequestMapping 註解的方法進行適配。該類的邏輯我暫時沒看懂,就不多說了,十分尷尬。關於該類比較詳細的分析,大家可以參考《看透Spring MVC》一書。

發佈了20 篇原創文章 · 獲贊 13 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章