在使用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的第一步。
dispatcherServletorg.springframework.web.servlet.DispatcherServletcontextConfigLocation/WEB-INF/dispatcherServlet-servlet.xmldispatcherServlet*.do
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.WEBAPPLICATIONCONTEXT_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),并将它綁定到一個屬性上(預設屬性名為WEBAPPLICATIONCONTEXT_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和[email protected]({ 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 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 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中的配置操作。這也是它的友善之處。
本文首發來自微信公衆号:程式新視界。一個軟實力、硬技術同步學習的平台。