1. 程式人生 > >Spring MVC 原理探秘 - 一個請求的旅行過程

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

方法體 業務 ansi exception 關系圖 禁止 能力 abs des

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 接口的定義,如下:

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。比如:

<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 接口定義,如下:

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。如下:

<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 部分方法的源碼:

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 方法了。下面我們看一下這個方法的實現。如下:

@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 返回一些系統信息。代碼如下:

@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 接口配合使用,比如下面的實例:

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 方法。這裏我把請求的處理流程圖再貼一遍,大家可以對著流程圖閱讀源碼。

技術分享圖片

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》一書。

4.總結

到此,本篇文章的主體內容就說完了。本篇文章從一個請求的旅行過程進行分析,並在分析的過程中補充了 Servlet 和 DispatcherServlet 方面的知識。在最後,從源碼的角度分析了 DispatcherServlet 處理請求的過程。總的來算,算是做到了循序漸進。當然,限於個人能力,以上內容可能會有一些講的不好的地方,這裏請大家見諒。同時,也希望大家多多指教。

好了,本篇文章先到這裏。謝謝大家的閱讀。

參考

  • 《看透Spring MVC》- 韓路彪
  • 《JSP & Servlet學習筆記》- 林信良

附錄:Spring 源碼分析文章列表

Ⅰ. IOC

更新時間 標題
2018-05-30 Spring IOC 容器源碼分析系列文章導讀
2018-06-01 Spring IOC 容器源碼分析 - 獲取單例 bean
2018-06-04 Spring IOC 容器源碼分析 - 創建單例 bean 的過程
2018-06-06 Spring IOC 容器源碼分析 - 創建原始 bean 對象
2018-06-08 Spring IOC 容器源碼分析 - 循環依賴的解決辦法
2018-06-11 Spring IOC 容器源碼分析 - 填充屬性到 bean 原始對象
2018-06-11 Spring IOC 容器源碼分析 - 余下的初始化工作

Ⅱ. AOP

更新時間 標題
2018-06-17 Spring AOP 源碼分析系列文章導讀
2018-06-20 Spring AOP 源碼分析 - 篩選合適的通知器
2018-06-20 Spring AOP 源碼分析 - 創建代理對象
2018-06-22 Spring AOP 源碼分析 - 攔截器鏈的執行過程

Ⅲ. MVC

更新時間 標題
2018-06-29 Spring MVC 原理探秘 - 一個請求的旅行過程
2018-06-30 Spring MVC 原理探秘 - 容器的創建過程

本文在知識共享許可協議 4.0 下發布,轉載需在明顯位置處註明出處
作者:coolblog.xyz
本文同步發布在我的個人博客:http://www.coolblog.xyz

技術分享圖片
本作品采用知識共享署名-非商業性使用-禁止演繹 4.0 國際許可協議進行許可。

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