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工作流程

image

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 視頻教程全家桶》


程序新視界:精彩和成長都不容錯過
![程序新視界-微信公眾號](https://img2018.cnblogs.com/blog/1742867/201910/1742867-20191013111755842-2090947098.png)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM