從源碼角度瞭解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
  • .Net8.0 Blazor Hybird 桌面端 (WPF/Winform) 實測可以完整運行在 win7sp1/win10/win11. 如果用其他工具打包,還可以運行在mac/linux下, 傳送門BlazorHybrid 發佈為無依賴包方式 安裝 WebView2Runtime 1.57 M ...
  • 目錄前言PostgreSql安裝測試額外Nuget安裝Person.cs模擬運行Navicate連postgresql解決方案Garnet為什麼要選擇Garnet而不是RedisRedis不再開源Windows版的Redis是由微軟維護的Windows Redis版本老舊,後續可能不再更新Garne ...
  • C#TMS系統代碼-聯表報表學習 領導被裁了之後很快就有人上任了,幾乎是無縫銜接,很難讓我不想到這早就決定好了。我的職責沒有任何變化。感受下來這個系統封裝程度很高,我只要會調用方法就行。這個系統交付之後不會有太多問題,更多應該是做小需求,有大的開發任務應該也是第二期的事,嗯?怎麼感覺我變成運維了?而 ...
  • 我在隨筆《EAV模型(實體-屬性-值)的設計和低代碼的處理方案(1)》中介紹了一些基本的EAV模型設計知識和基於Winform場景下低代碼(或者說無代碼)的一些實現思路,在本篇隨筆中,我們來分析一下這種針對通用業務,且只需定義就能構建業務模塊存儲和界面的解決方案,其中的數據查詢處理的操作。 ...
  • 對某個遠程伺服器啟用和設置NTP服務(Windows系統) 打開註冊表 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\W32Time\TimeProviders\NtpServer 將 Enabled 的值設置為 1,這將啟用NTP伺服器功 ...
  • title: Django信號與擴展:深入理解與實踐 date: 2024/5/15 22:40:52 updated: 2024/5/15 22:40:52 categories: 後端開發 tags: Django 信號 松耦合 觀察者 擴展 安全 性能 第一部分:Django信號基礎 Djan ...
  • 使用xadmin2遇到的問題&解決 環境配置: 使用的模塊版本: 關聯的包 Django 3.2.15 mysqlclient 2.2.4 xadmin 2.0.1 django-crispy-forms >= 1.6.0 django-import-export >= 0.5.1 django-r ...
  • 今天我打算整點兒不一樣的內容,通過之前學習的TransformerMap和LazyMap鏈,想搞點不一樣的,所以我關註了另外一條鏈DefaultedMap鏈,主要調用鏈為: 調用鏈詳細描述: ObjectInputStream.readObject() DefaultedMap.readObject ...
  • 後端應用級開發者該如何擁抱 AI GC?就是在這樣的一個大的浪潮下,我們的傳統的應用級開發者。我們該如何選擇職業或者是如何去快速轉型,跟上這樣的一個行業的一個浪潮? 0 AI金字塔模型 越往上它的整個難度就是職業機會也好,或者說是整個的這個運作也好,它的難度會越大,然後越往下機會就會越多,所以這是一 ...
  • @Autowired是Spring框架提供的註解,@Resource是Java EE 5規範提供的註解。 @Autowired預設按照類型自動裝配,而@Resource預設按照名稱自動裝配。 @Autowired支持@Qualifier註解來指定裝配哪一個具有相同類型的bean,而@Resourc... ...