從源碼角度瞭解SpringMVC的執行流程

来源:https://www.cnblogs.com/LuxBai/archive/2020/01/11/12180668.html
-Advertisement-
Play Games

從源碼角度瞭解SpringMVC的執行流程 SpringMVC的執行流程網上有很多帖子都有講解,流程圖和文字描述都很詳細,但是你如果沒有通過具體源碼自己走一遍流程,其實只是死記硬背。所以想開個帖子從源碼角度再梳理一遍SpringMVC的執行流程,加深印象。 [TOC] SpringMVC介紹 Spr ...


目錄

從源碼角度瞭解SpringMVC的執行流程

  SpringMVC的執行流程網上有很多帖子都有講解,流程圖和文字描述都很詳細,但是你如果沒有通過具體源碼自己走一遍流程,其實只是死記硬背。所以想開個帖子從源碼角度再梳理一遍SpringMVC的執行流程,加深印象。

SpringMVC介紹

  SpringMVC採用的是前端控制器(Front Controller) + 各個業務處理器(Controller)來處理請求的。前端控制器來響應所有請求,通過一定的調度規則找到具體負責處理的業務處理器,並將請求委派給具體的業務處理器去執行業務邏輯,業務處理器返回給前端控制器模型數據model,最後前端控制器將model交給視圖View進行渲染。

源碼分析思路

  看源碼的同學可能往往會陷入一個怪圈,剛開始看可能還能看懂,等到一層一層點進去會越來越暈,讓自己陷入了太多的細節中,而這些細節其實對主要流程並沒有多大影響,然後就埋頭研究。之後不得不又從頭開始看,又讓自己陷入了另一個細節。其實看源碼開始時只是需要看一個大致的框架和思路,瞭解代碼的大致執行流程,千萬不要讓自己陷入到細節的泥潭中。所以本文是通過幾個關鍵的介面作為切入點來梳理SpringMVC的執行流程,如果我們把關鍵的介面弄懂了,也就瞭解了SpringMVC的執行流程。所以本文只是去瞭解介面功能,並不關註到具體的實現邏輯上。當我們把大體流程瞭解後,之後就只是各個擊破具體的實現類了。之後作者還會通過自己來實現這些介面來處理自己定義的請求,結合具體的例子來理解。

閱讀SpringMVC源碼給我最大的感觸有兩點:

  • 開放封閉原則,SpringMVC中可擴展性很強,我們只需要實現具體的介面,然後將介面加入到容器中,就可以實現我們的擴展功能,不需要改任何代碼,對擴展開放。而且已有實現類中的關鍵方法都是用final修飾的,對修改關閉。
  • 面向介面編程,所有重要的流程代碼,幾乎都是介面調用,而不是具體到某一個特定的類上面。

源碼解讀

註:源碼版本為 spring-webmvc-5.2.2.RELEASE.jar

幾個關鍵介面和類

HandlerMapping

public interface HandlerMapping {
    @Nullable
    HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;

}

HandlerMapping映射了請求與具體處理器的關係,可以理解為記憶體中有這樣一個記憶體數據 Map<request, Handler>,HandlerMapping就是根據請求從Map中找到Handler返回。HandlerExecutionChain只是將Handler和其對應的攔截器interceptors進行了包裝。

前文所說的調度規則,通過請求的 HttpServletRequest 獲取具體的請求處理類,將請求處理類包裝成 HandlerExecutionChain 返回。其中 HandlerExecutionChain中的Object handler就是具體的請求處理類。

public class HandlerExecutionChain {
    
    private final Object handler;

    @Nullable
    private HandlerInterceptor[] interceptors;

    @Nullable
    private List<HandlerInterceptor> interceptorList;
}

我們現在通過請求找到了具體的處理類,那麼我們怎麼通過處理類去執行具體的方法呢?那麼就需要HandlerAdapter了。

HandlerAdapter

public interface HandlerAdapter {
    
    /**
     * 通過方法 supports 判斷適配器是否適配這種類型的Handler,返回true則代表適配。
     */
    boolean supports(Object handler);
    
    /**
     * 如果適配則通過方法 handle 去讓 Object handler 執行具體的處理方法。
     */
    @Nullable
    ModelAndView handle(HttpServletRequest request, HttpServletResponse response
                        , Object handler) throws Exception;
    
        long getLastModified(HttpServletRequest request, Object handler);

}

HandlerAdapter 處理器的適配器,幫助前端控制器去執行處理器Handler中具體的處理業務,讓前端控制機不需要關註具體的執行細節,也就是說HandlerAdapter對前端控制機屏蔽了處理器執行的具體細節。

ModelAndView

public class ModelAndView {

    /** View instance or view name String. */
    @Nullable
    private Object view;

    /** Model Map. */
    @Nullable
    private ModelMap model;

對邏輯視圖名view和數據模型的封裝。

Object view為通常為String類型的邏輯視圖名。

ModelMap 為MVC中Model的角色,底層為Map類型。

ViewResolver

public interface ViewResolver {
    @Nullable
    View resolveViewName(String viewName, Locale locale) throws Exception;

}

解析邏輯視圖名viewName找到具體的View,前端控制器找到具體視圖View的嚮導。

View

public interface View {
        void render(@Nullable Map<String, ?> model, HttpServletRequest request
                    , HttpServletResponse response) throws Exception;

}

調用render方法通過數據模型渲染視圖。

請求參數中的 Map<String, ?> model 在SpringMVC中扮演Model的角色。

比如我們想返回json格式的數據,那麼render方法邏輯就是將model轉為json格式輸出。

或者我們想返回jsp,那麼我們就可以model解析到具體的jsp頁面進行展示。

前端控制器 DispatcherServlet

 在SpringMVC中,DispatcherServlet作為前端控制器,控制服務的具體執行流程,主要的執行流程代碼也都在這個類中。

下麵是DispatcherServlet簡化的源碼,只包含了重要的執行流程,保留了關鍵的執行代碼,讀者可以具體關鍵字進行搜索去找到具體的代碼行。

public class DispatcherServlet extends FrameworkServlet {
    protected void doDispatch(HttpServletRequest request, 
                              HttpServletResponse response) throws Exception {
        HandlerExecutionChain mappedHandler = null;
        ModelAndView mv = null;
        mappedHandler = getHandler(processedRequest);
        HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
        mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
        
        render(mv, request, response);
    }
    
    protected void render(ModelAndView mv, HttpServletRequest request
                          , HttpServletResponse response) throws Exception {
        String viewName = mv.getViewName();
        view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
        view.render(mv.getModelInternal(), request, response);
    }
代碼流程:
  • 獲取處理器:HandlerMapping通過HttpServletRequest請求找到具體的Handler
  • 獲取處理器對應的適配器:通過Handler找到具體的HandlerAdapter
  • 調用處理器的處理邏輯:HandlerAdapter調用Handler執行具體的處理邏輯,返回ModelAndView
  • 解析視圖:ViewResolver通過ModelAndView中的邏輯視圖名找到具體的View。
  • 渲染視圖:View將數據模型進行渲染。
獲取處理器
 @Nullable
    protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        if (this.handlerMappings != null) {
            for (HandlerMapping mapping : this.handlerMappings) {
                HandlerExecutionChain handler = mapping.getHandler(request);
                if (handler != null) {
                    return handler;
                }
            }
        }
        return null;
    }

SpringMVC在啟動的時候會掃描所有實現了HandlerMapping介面的類,並將這些類加入到容器中。

獲取處理器其實就是迴圈實現了HandlerMapping的類,調用getHandler()方法,找到了就停止並返回。

每種類型的Handler都有各自對應的HandlerMapping。比如SpirngMVC中預設的處理器為 Controller,與之對於的HandlerMapping為RequestMappingHandlerMapping。

獲取處理器的適配器
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
        if (this.handlerAdapters != null) {
            for (HandlerAdapter adapter : this.handlerAdapters) {
                if (adapter.supports(handler)) {
                    return adapter;
                }
            }
        }
}

邏輯和獲取處理器一樣,判斷邏輯就是前面提過的,只要supports方法返回true,則代表適配這個Handler。

同理也是一種Handler應該有與之對應的HandlerAdapter。與Controller對應的為RequestMappingHandlerAdapter。

所以如果我們要編寫自定義的處理器。那麼我們需要自己的Handler類和與之對於的HandlerMapping和HandlerAdapter。

解析視圖
 @Nullable
    protected View resolveViewName(String viewName, @Nullable Map<String, Object> model,
            Locale locale, HttpServletRequest request) throws Exception {

        if (this.viewResolvers != null) {
            for (ViewResolver viewResolver : this.viewResolvers) {
                View view = viewResolver.resolveViewName(viewName, locale);
                if (view != null) {
                    return view;
                }
            }
        }
        return null;
    }

仍然是同樣的邏輯,所有的代碼都是面向介面來開發的。

SpringMVC支持各種各樣的視圖渲染,如JSP、json、freemarker、thymeleaf。ViewResolver就是這些視圖的嚮導,它告訴SpringMVC需要通過什麼方式去找到具體的視圖View。

每種視圖View都有自己對應的視圖解析器,例如FreeMarkerView對應的視圖解析器為FreeMarkerViewResolver。

視圖渲染

其實就一句代碼。

view.render(mv.getModelInternal(), request, response);

視圖渲染,作者剛開始看到這個詞,覺得好高大上。其實就是讓數據模型model應以什麼樣的方式來展示。

如果你想將model以json格式返回,那麼你就去實現View介面,把model轉為json格式,然後寫入到響應類的輸出流即可。

ServletOutputStream out = response.getOutputStream();
baos.writeTo(json);
out.flush();

結語

本文只是通過幾個重要的介面來描述SpringMVC的執行流程,沒有具體分析實現類的邏輯。也想在這裡分享下自己看源碼的心得體會。看源碼時千萬不要讓自己陷入過深的業務邏輯中去,先看主要執行流程,重要的介面,比如以debug的方式先預覽下執行的方法棧,根據方法棧去定位。如果有哪些地方有誤或者有不同的理解,還請不吝賜教。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • python裝飾器@wraps作用 修複被裝飾後的函數名等屬性的改變 Python裝飾器(decorator)在實現的時候,被裝飾後的函數其實已經是另外一個函數了(函數名等函數屬性會發生改變), 為了不影響,Python的functools包中提供了一個叫wraps的decorator來消除這樣的副 ...
  • 怎麼快速的對列表進行去重呢,去重之後原來的順序會不會改變呢? 去重之後順序會改變 set去重 列表去重改變原列表的順序了 但是,可以通過列表中索引(index)的方法保證去重後的順序不變。 itertools.groupby fromkeys 通過刪除索引 去重不改變順序 建立新列表[] reduc ...
  • 功能介紹 整理生信小知識庫,一些技巧一些知識。 昨天 以下配置環境基於window操作系統,安裝python3版本為例,推薦基礎版配置。 ! METHOD 1 (基礎版) 官網下載對應電腦版本的python3:https://www.python.org/downloads/windows/​​ 下 ...
  • 小伙伴們新年好啊,又有半個月沒有更新博客了。更新也比較隨性,想起什麼就寫點什麼,方便和大家工作同學習總結。 最近和同事說起了PHP安全相關的問題,記錄下一些心得體會。 由於腳本語言和早期版本設計的諸多原因,php項目存在不少安全隱患。從配置選項來看,可以做如下的優化。 1.屏蔽PHP錯誤輸出。在/e ...
  • 在指定範圍內生成一個隨機數作為目標值,用戶對目標值進行猜測。 import java.util.Random; // 隨機數 import java.util.Scanner; // 獲取用戶輸入 public class Example { public static void main(Stri ...
  • 生成指定範圍內的隨機數 Math.random() 生成隨機數,隨機數在0到1之間,類型是 double。 public class randCase { public static void main(String[] args) { double rand = 0; for (int i = 0 ...
  • 簡介 AspectJ是一個基於Java語言的AOP框架,Spring2.0以後新增了對AspectJ切點表達式支持。因為Spring1.0的時候Aspectj還未出現; AspectJ1.5中新增了對註解的支持,允許直接在Bean類中定義切麵。新版本的Spring框架建 議我們都使用AspectJ方 ...
  • 在寫 Python 項目的時候,我們可能經常會遇到導入模塊失敗的錯誤:ImportError: No module named 'xxx'或者ModuleNotFoundError: No module named 'xxx'。 導入失敗問題,通常分為兩種:一種是導入自己寫的模塊(即以 .py 為後 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...