1. 程式人生 > >SpringBoot之DispatcherServlet詳解及原始碼解析

SpringBoot之DispatcherServlet詳解及原始碼解析

在使用SpringBoot之後,我們表面上已經無法直接看到DispatcherServlet的使用了。本篇文章,帶大家從最初DispatcherServlet的使用開始到SpringBoot原始碼中DispatcherServlet的自動配置進行詳解。

DispatcherServlet簡介

DispatcherServlet是前端控制器設計模式的實現,提供了Spring Web MVC的集中訪問點,而且負責職責的分派,而且與Spring Ioc容器無縫整合,從而可以獲得Spring的所有好處。

DispatcherServlet作用

DispatcherServlet主要用作職責排程工作,本身主要用於控制流程,主要職責如下:

  • 檔案上傳解析,如果請求型別是multipart將通過MultipartResolver進行檔案上傳解析;
  • 通過HandlerMapping,將請求對映到處理器(返回一個HandlerExecutionChain,它包括一個處理器、多個HandlerInterceptor攔截器);
  • 通過HandlerAdapter支援多種型別的處理器(HandlerExecutionChain中的處理器);
  • 通過ViewResolver解析邏輯檢視名到具體檢視實現;
  • 本地化解析;
  • 渲染具體的檢視等;
  • 如果執行過程中遇到異常將交給HandlerExceptionResolver來解析。

DispatcherServlet工作流程

DispatcherServlet傳統配置

DispatcherServlet作為前置控制器,通常配置在web.xml檔案中的。攔截匹配的請求,Servlet攔截匹配規則要自已定義,把攔截下來的請求,依據相應的規則分發到目標Controller來處理,是配置spring MVC的第一步。

<servlet>
    <servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/dispatcherServlet-servlet.xml</param-value> 
    </init-param>
</servlet> 
<servlet-mapping>
     <servlet-name>dispatcherServlet</servlet-name>
     <url-pattern>*.do</url-pattern> 
</servlet-mapping>

DospatcherServlet實際上是一個Servlet(它繼承HttpServlet)。DispatcherServlet處理的請求必須在同一個web.xml檔案裡使用url-mapping定義對映。這是標準的J2EE servlet配置。

在上述配置中:

  • servlet-name用來定義servlet的名稱,這裡是dispatcherServlet。
  • servlet-class用來定義上面定義servlet的具體實現類,這裡是org.springframework.web.servlet.DispatcherServlet。
  • init-param用來定義servlet的初始化引數,這裡指定要初始化WEB-INF資料夾下的dispatcherServlet-servlet.xml。如果spring-mvc.xml的命名方式是前面定義servlet-name+"-servlet",則可以不用定義這個初始化引數,(Spring預設配置檔案為“/WEB-INF/[servlet名字]-servlet.xml”),Spring會處理這個配置檔案。由此可見,Spring的配置檔案也可放置在其他位置,只要在這裡指定就可以了。如果定義了多個配置檔案,則用“,”分隔即可。
  • servlet-mapping定義了所有以.do結尾的請求,都要經過分發器。

當DispatcherServlet配置好後,一旦DispatcherServlet接受到請求,DispatcherServlet就開始處理請求了。

DispatcherServlet處理流程

當配置好DispatcherServlet後,DispatcherServlet接收到與其對應的請求之時,處理就開始了。處理流程如下:

找到WebApplicationContext並將其繫結到請求的一個屬性上,以便控制器和處理鏈上的其它處理器能使用WebApplicationContext。預設的屬性名為DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE。

將本地化解析器繫結到請求上,這樣使得處理鏈上的處理器在處理請求(準備資料、顯示檢視等等)時能進行本地化處理。如果不需要本地化解析,忽略它就可以了。

將主題解析器繫結到請求上,這樣檢視可以決定使用哪個主題。如果你不需要主題,可以忽略它。

如果你指定了一個上傳檔案解析器,Spring會檢查每個接收到的請求是否存在上傳檔案,如果是,這個請求將被封裝成MultipartHttpServletRequest以便被處理鏈中的其它處理器使用。(Spring's multipart (fileupload) support檢視更詳細的資訊)

找到合適的處理器,執行和這個處理器相關的執行鏈(前處理器,後處理器,控制器),以便為檢視準備模型資料。

如果模型資料被返回,就使用配置在WebApplicationContext中的檢視解析器顯示檢視,否則檢視不會被顯示。有多種原因可以導致返回的資料模型為空,比如前處理器或後處理器可能截取了請求,這可能是出於安全原因,也可能是請求已經被處理過,沒有必要再處理一次。

DispatcherServlet相關原始碼

org.springframework.web.servlet.DispatcherServlet中doService方法部分原始碼:

protected void doService(HttpServletRequest request,
            HttpServletResponse response) throws Exception {
    // ......

    request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE,getWebApplicationContext());
    // ......
}

通過上面原始碼得知,DispatcherServlet會找到上下文WebApplicationContext(其指定的實現類為XmlWebApplicationContext),並將它繫結到一個屬性上(預設屬性名為WEB_APPLICATION_CONTEXT_ATTRIBUTE),以便控制器能夠使用WebApplicationContext。

initStrategies方法原始碼如下:

protected void initStrategies(ApplicationContext context) {
    initMultipartResolver(context);
    initLocaleResolver(context);
    initThemeResolver(context);
    initHandlerMappings(context);
    initHandlerAdapters(context);
    initHandlerExceptionResolvers(context);
    initRequestToViewNameTranslator(context);
    initViewResolvers(context);
    initFlashMapManager(context);
}

從如上程式碼可以看出,DispatcherServlet啟動時會進行我們需要的Web層Bean的配置,如HandlerMapping、HandlerAdapter等,而且如果我們沒有配置,還會給我們提供預設的配置。

DispatcherServlet SpringBoot自動配置

DispatcherServlet在Spring Boot中的自動配置是通過DispatcherServletAutoConfiguration類來完成的。

先看註解部分程式碼:

@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass(DispatcherServlet.class)
@AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class)
public class DispatcherServletAutoConfiguration {
    ...
}

@AutoConfigureOrder指定該自動配置的優先順序;@Configuration指定該類為自動配置類;@ConditionalOnWebApplication指定自動配置需要滿足是基於SERVLET的web應用;@ConditionalOnClass指定類路徑下必須有DispatcherServlet類存在;@AutoConfigureAfter指定該自動配置必須基於ServletWebServerFactoryAutoConfiguration的自動配置。

DispatcherServletAutoConfiguration中關於DispatcherServlet例項化的程式碼如下:

@Configuration(proxyBeanMethods = false) // 例項化配置類
@Conditional(DefaultDispatcherServletCondition.class) // 例項化條件:通過該類來判斷
@ConditionalOnClass(ServletRegistration.class) // 存在指定的ServletRegistration類
// 載入HttpProperties和WebMvcProperties
@EnableConfigurationProperties({ HttpProperties.class, WebMvcProperties.class })
protected static class DispatcherServletConfiguration {

    @Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
    public DispatcherServlet dispatcherServlet(HttpProperties httpProperties, WebMvcProperties webMvcProperties) {
        // 建立DispatcherServlet
        DispatcherServlet dispatcherServlet = new DispatcherServlet();
        // 初始化DispatcherServlet各項配置
        dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest());
        dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest());
        dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound());
        dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents());
        dispatcherServlet.setEnableLoggingRequestDetails(httpProperties.isLogRequestDetails());
        return dispatcherServlet;
    }

    // 初始化上傳檔案的解析器
    @Bean
    @ConditionalOnBean(MultipartResolver.class)
    @ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)
    public MultipartResolver multipartResolver(MultipartResolver resolver) {
        // Detect if the user has created a MultipartResolver but named it incorrectly
        return resolver;
    }

}

內部類DispatcherServletConfiguration同樣需要滿足指定的條件才會進行初始化,具體看程式碼中的註釋。

其中的dispatcherServlet方法中實現了DispatcherServlet的例項化,並設定了基礎引數。這對照傳統的配置就是web.xml中DispatcherServlet的配置。

另外一個方法multipartResolver,用於初始化上傳檔案的解析器,主要作用是當用戶定義的MultipartResolver名字不為“multipartResolver”時,通過該方法將其修改為“multipartResolver”,相當於重新命名。

其中DispatcherServletConfiguration的註解@Conditional限定必須滿足DefaultDispatcherServletCondition定義的匹配條件才會自動配置。而DefaultDispatcherServletCondition類同樣為內部類。

@Order(Ordered.LOWEST_PRECEDENCE - 10)
private static class DefaultDispatcherServletCondition extends SpringBootCondition {

    @Override
    public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
        ConditionMessage.Builder message = ConditionMessage.forCondition("Default DispatcherServlet");
        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
        List<String> dispatchServletBeans = Arrays
                .asList(beanFactory.getBeanNamesForType(DispatcherServlet.class, false, false));
        if (dispatchServletBeans.contains(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)) {
            return ConditionOutcome
                    .noMatch(message.found("dispatcher servlet bean").items(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME));
        }
        if (beanFactory.containsBean(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)) {
            return ConditionOutcome.noMatch(
                    message.found("non dispatcher servlet bean").items(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME));
        }
        if (dispatchServletBeans.isEmpty()) {
            return ConditionOutcome.match(message.didNotFind("dispatcher servlet beans").atAll());
        }
        return ConditionOutcome.match(message.found("dispatcher servlet bean", "dispatcher servlet beans")
                .items(Style.QUOTE, dispatchServletBeans)
                .append("and none is named " + DEFAULT_DISPATCHER_SERVLET_BEAN_NAME));
    }

}

該類的核心功能,總結起來就是:檢驗Spring容器中是否已經存在一個名字為“dispatcherServlet”的DispatcherServlet,如果不存在,則滿足條件。

在該自動配置類中還有用於例項化ServletRegistrationBean的內部類:

@Configuration(proxyBeanMethods = false)
@Conditional(DispatcherServletRegistrationCondition.class)
@ConditionalOnClass(ServletRegistration.class)
@EnableConfigurationProperties(WebMvcProperties.class)
@Import(DispatcherServletConfiguration.class)
protected static class DispatcherServletRegistrationConfiguration {

    @Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
    @ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
    public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet,
            WebMvcProperties webMvcProperties, ObjectProvider<MultipartConfigElement> multipartConfig) {
        // 通過ServletRegistrationBean將dispatcherServlet註冊為servlet,這樣servlet才會生效。
        DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet,
                webMvcProperties.getServlet().getPath());
        // 設定名稱為dispatcherServlet
        registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
        // 設定載入優先順序,設定值預設為-1,存在於WebMvcProperties類中
        registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
        multipartConfig.ifAvailable(registration::setMultipartConfig);
        return registration;
    }

}

DispatcherServletRegistrationConfiguration類的核心功能就是註冊dispatcherServlet使其生效並設定一些初始化的引數。

其中,DispatcherServletRegistrationBean繼承自ServletRegistrationBean,主要為DispatcherServlet提供服務。DispatcherServletRegistrationBean和DispatcherServlet都提供了註冊Servlet並公開DispatcherServletPath資訊的功能。

Spring Boot通過上面的自動配置類就完成了之前我們在web.xml中的配置操作。這也是它的方便之處。

參考文章:

https://www.cnblogs.com/wql025/p/4805634.html

https://juejin.im/post/5d3066736fb9a07ece6806e4

原文連結:《SpringBoot之DispatcherServlet詳解及原始碼解析》

Spring技術視訊

CSDN學院:《Spring Boot 視訊教程全家桶》


程式新視界:精彩和成長都不容錯過

相關推薦

SpringBootDispatcherServlet原始碼解析

在使用SpringBoot之後,我們表面上已經無法直接看到DispatcherServlet的使用了。本篇文章,帶大家從最初DispatcherServlet的使用開始到SpringBoot原始碼中DispatcherServlet的自動配置進行詳解。 DispatcherServlet簡介 Dispatch

【MapReduce原始碼解析(一)】——分片輸入、MapperMap端Shuffle過程

title: 【MapReduce詳解及原始碼解析(一)】——分片輸入、Mapper及Map端Shuffle過程 date: 2018-12-03 21:12:42 tags: Hadoop categories: 大資料 toc: true 點選檢視我的部落格:Josonlee’

Android應用Context原始碼解析

轉自 http://blog.csdn.net/yanbober/article/details/45967639  1  背景 今天突然想起之前在上家公司(做TV與BOX盒子)時有好幾個人問過我關於Android的Context到底是啥的問題,所以就馬上

OKHttp使用原始碼解析

前言 上一篇部落格瞭解了Retrofit的使用,它是對OKHttp的封裝,且Retrofit2的內部實現是OKHttp3,下面就瞭解一下OKHttp3的使用! 使用 ①首先匯入依賴,最新的版本是3.4.1,在gradle中: compile 'com

SpringBoot Profile使用配置原始碼解析

在實踐的過程中我們經常會遇到不同的環境需要不同配置檔案的情況,如果每換一個環境重新修改配置檔案或重新打包一次會比較麻煩,Spring Boot為此提供了Profile配置來解決此問題。 Profile的作用 Profile對應中文並沒有合適的翻譯,它的主要作用就是讓Spring Boot可以根據不同環境提供不

Spring IOC容器啟動流程原始碼解析(一)——容器概念原始碼初探

目錄 1. 前言 1.1 IOC容器到底是什麼 IOC和AOP是Spring框架的核心功能,而IOC又是AOP實現的基礎,因而可以說IOC是整個Spring框架的基石。那麼什麼是IOC?IOC即控制反轉,通俗的說就是讓Spring框架來幫助我們完成物件的依賴管理和生命週期控制等等工作。從面向物件的角度來說,

MyBatisMapper常用技巧

mybatis mapperselect先看一個簡單的案例:<select id="selectPerson" parameterType="int" resultType="hashmap"> SELECT * FROM PERSON WHERE ID = #{id} </selec

跨鏈技術ILP應用

區塊鏈;跨鏈;托管;ilp;rippleAbstract:As the booming of BlockChain technology, the requirement of asset transfer between different ledgers is as imperative as possi

Ansible自動化運維Playbook體驗(二)

tasks ansible sha shadow yml ESS remote name 自動化運維 Handlers介紹: Handlers也是一些task的列表,和一般的task並沒有什麽區別。是由通知者進行的notify,如果沒有被notify,則Handlers

Show, attend and tell演算法原始碼

mark一下,感謝作者分享! https://blog.csdn.net/shenxiaolu1984/article/details/51493673 原論文:https://arxiv.org/pdf/1502.03044v2.pdf 原始碼:https://github.c

Spring全家桶系列–SpringBootAOP

//本文作者:cuifuan //本文將收錄到選單欄:《Spring全家桶》專欄中 面向方面程式設計(AOP)通過提供另一種思考程式結構的方式來補充面向物件程式設計(OOP)。 OOP中模組化的關鍵單元是類,而在AOP中,模組化單元是方面。 準備工作 首先,使用AOP要在bu

SpringBootAOP

面向方面程式設計(AOP)通過提供另一種思考程式結構的方式來補充面向物件程式設計(OOP)。 OOP中模組化的關鍵單元是類,而在AOP中,模組化單元是方面。 準備工作 首先,使用AOP要在build.gradle中加入依賴 //引入AOP依賴 compile "org

Android非同步訊息處理機制原始碼分析

PS一句:最終還是選擇CSDN來整理髮表這幾年的知識點,該文章平行遷移到CSDN。因為CSDN也支援MarkDown語法了,牛逼啊! 【工匠若水 http://blog.csdn.net/yanbober 轉載煩請註明出處,尊重分享成果】 最近相對來說比較閒,加上養病,所

Type的子介面原始碼解析

以下是原始碼中對Type的註釋:Type是Java中所有型別的常見的超介面,在程式語言中這些包括原始型別,引數化的型別,陣列型別,型別變數和原始型別。 Class在一定程度上挽救了擦除的型別資訊,我們就可以通過這幾個介面來獲取被擦除的型別引數資訊,這幾個介面無非就是對型別引數的一個

Android應用開發Scroller原始碼淺析

1 背景 大家都知道Android View提供了scrollTo()與scrollBy()方法來供我們進行View的滾動,但是有個問題就是他的滾動很蛋疼,疼在是瞬時挪動到指定位置的,這種對於追求使用者體驗的今天來說簡直是硬傷啊;為了解決這個問題Google給

Cocoa包管理器CarthageCocoaPods中心化+Carthage的二進位制化

上篇部落格詳細的聊了的相關內容,今天我們就來介紹另一個Cocoa的包管理器Carthage。在上家公司用Swift開發工程時,用的就是Carthage。Carthage誕生於14年11月份,是用Swift語言開發的,相對於CocoaPods來說是一個新生事物。本篇部落格主要介紹一下Carthage的使用姿勢,

Struts2實現單檔案的上傳功能例項原始碼

  Struts2完成檔案的上傳功能例項 10級學員 郞志課堂筆記 在這裡通過一個例項簡單寫一下struts2實現檔案上傳的流程。 首先通過手寫的方式寫一個上傳的流程 第一步:建立相應的jsp頁面 <%@ page language="java" import="ja

SparseArray原始碼簡析

一、前言 SparseArray 是 Android 在 Android SdK 為我們提供的一個基礎的資料結構,其功能類似於 HashMap。與 HashMap 不同的是它的 Key 只能是 int 值,不能是其他的型別。 二、程式碼分析 1. demo 及其簡析 首先也還是先通過 demo 來看一

ER圖例項解析

ER圖分為實體、屬性、關係三個核心部分。 實體是長方形,屬性是橢圓形,關係為菱形。 實體(entity): 即資料模型中的資料物件(即資料表),用長方體來表示,每個實體都有自己的實體成員(entity member)或者說實體物件(entity instance),例如

依賴注入框架 ----Dagger2 使用原始碼分析

在開始說Dagger之前先說下什麼叫依賴注入。 依賴: 在建立物件A的過程中,需要用到物件B的例項,這種情況較呼叫者A對被呼叫者B有一個依賴。 例如下面的例子: 組裝一臺電腦時,要用到Cpu,那麼電腦這個物件,依賴Cpu物件。 public cl