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