SpringBoot之DispatcherServlet詳解及源碼解析

在使用SpringBoot以後,咱們表面上已經沒法直接看到DispatcherServlet的使用了。本篇文章,帶你們從最初DispatcherServlet的使用開始到SpringBoot源碼中DispatcherServlet的自動配置進行詳解。html

DispatcherServlet簡介

DispatcherServlet是前端控制器設計模式的實現,提供了Spring Web MVC的集中訪問點,並且負責職責的分派,並且與Spring Ioc容器無縫集成,從而能夠得到Spring的全部好處。前端

DispatcherServlet做用

DispatcherServlet主要用做職責調度工做,自己主要用於控制流程,主要職責以下:web

  • 文件上傳解析,若是請求類型是multipart將經過MultipartResolver進行文件上傳解析;
  • 經過HandlerMapping,將請求映射處處理器(返回一個HandlerExecutionChain,它包括一個處理器、多個HandlerInterceptor攔截器);
  • 經過HandlerAdapter支持多種類型的處理器(HandlerExecutionChain中的處理器);
  • 經過ViewResolver解析邏輯視圖名到具體視圖實現;
  • 本地化解析;
  • 渲染具體的視圖等;
  • 若是執行過程當中遇到異常將交給HandlerExceptionResolver來解析。

DispatcherServlet工做流程

image

DispatcherServlet傳統配置

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

<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配置。設計模式

在上述配置中:spring-mvc

  • 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接收到與其對應的請求之時,處理就開始了。處理流程以下:springboot

找到WebApplicationContext並將其綁定到請求的一個屬性上,以便控制器和處理鏈上的其它處理器能使用WebApplicationContext。默認的屬性名爲DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE。微信

將本地化解析器綁定到請求上,這樣使得處理鏈上的處理器在處理請求(準備數據、顯示視圖等等)時能進行本地化處理。若是不須要本地化解析,忽略它就能夠了。mvc

將主題解析器綁定到請求上,這樣視圖能夠決定使用哪一個主題。若是你不須要主題,能夠忽略它。

若是你指定了一個上傳文件解析器,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 視頻教程全家桶》


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

程序新視界-微信公衆號

相關文章
相關標籤/搜索