DispatcherServlet
DispatcherServlet
是Spring MVC的前端控制器名稱, 使用者的請求到達這裡進行集中處理, 在Spring MVC中, 它的作用是為不同請求匹配對應的處理器, 將結果傳遞給檢視解析器最終呈現給客戶端.
前端控制器模式(Front Controller Pattern)是用來提供一個集中的請求處理機制,所有的請求都將由一個單一的處理程式處理。該處理程式可以做認證/授權/記錄日誌,或者跟蹤請求,然後把請求傳給相應的處理程式。
Servlet WebApplicationContext 和 Root WebApplicationContext
Spring MVC 存在兩個應用上下文, 分別為Servlet WebApplicationContext和Root WebApplicationContext. 他們分別初始化不同型別的bean.
下圖來自Spring官方文件
在DispatcherServlet啟動的時候, 它會建立Spring上下文Servlet WebApplicationContext, 其中包含Web相關的Controller,ViewResolver,HandlerMapping等.
另外一個上下文Root WebApplicationContext是由ContextLoaderListener建立的, 包含除了Web元件外的其他bean, 比如包含業務邏輯的Service, 還有資料庫相關的元件等.
程式碼(JavaConfig方式的配置程式碼)
下面是用JavaConfig方式實現的配置程式碼, 我們先搭建好一個Spring MVC 專案,然後結合原始碼分析Spring如何註冊DispatcherServlet
例項的.
// 繼承AbstractAnnotationConfigDispatcherServletInitializer並重寫其中的三個方法
public class MvcWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
// 指定Root上下文的配置類
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{ RootConfig.class };
}
// 指定Web上下文的配置類
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{ WebConfig.class };
}
// url對映
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
通過重寫AbstractAnnotationConfigDispatcherServletInitializer
的三個方法完成配置, WebConfig
用來配置Web元件, RootConfig
用來配置非Web元件.
@EnableWebMvc // 啟用MVC
@ComponentScan(basePackages = {"com.xlx.mvc.web"}) // 啟用元件掃描,只掃描web相關的元件
@Configuration
public class WebConfig implements WebMvcConfigurer {
// 檢視解析器,jsp
@Bean
public ViewResolver viewResolver(){
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/views/");
resolver.setSuffix(".jsp");
resolver.setExposeContextBeansAsAttributes(true);
return resolver;
}
// 重寫以啟用預設的處理器, 用來處理靜態資源
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer){
configurer.enable();
}
}
@Configuration
@ComponentScan(basePackages = {"com.xlx.mvc"}, excludeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION,classes = EnableWebMvc.class)
}) // 掃描包, 但排除EnableWebMvc註解的類
public class RootConfig {
}
原始碼分析
Servlet 3.0 旨在支援基於程式碼的方式配置Servlet容器, 當3.0相容的servlet容器啟動的時候會在ClassPath查詢並呼叫實現了介面ServletContainerInitializer
的類的onStartup()
方法, Spring中提供了這個介面的一個實現類SpringServletContainerInitializer
. 其啟動方法的程式碼如下:
@Override
public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
throws ServletException {
List<WebApplicationInitializer> initializers = new LinkedList<>();
// 應用中WebApplicationInitializer的bean生成到一個列表中.
if (webAppInitializerClasses != null) {
for (Class<?> waiClass : webAppInitializerClasses) {
if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
try {
initializers.add((WebApplicationInitializer)
ReflectionUtils.accessibleConstructor(waiClass).newInstance());
}
catch (Throwable ex) {
throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
}
}
}
}
if (initializers.isEmpty()) {
servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
return;
}
servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
AnnotationAwareOrderComparator.sort(initializers);
// 遍歷所有WebApplicationInitializer, 並呼叫其onStartup方法
for (WebApplicationInitializer initializer : initializers) {
initializer.onStartup(servletContext);
}
}
在上面方法的最後, 可以看到其將控制權交給WebApplicationInitializer
的例項並遍歷呼叫了onStartup()
方法, 而我們定義的類MvcWebAppInitializer
就是它的子類. 完整的繼承關係為
WebApplicationInitializer
<--
AbstractContextLoaderInitializer
<--
AbstractDispatcherServletInitializer
<--
AbstractAnnotationConfigDispatcherServletInitializer
<--
MvcWebAppInitializer
在類 AbstractDispatcherServletInitializer
中實現了onStartup()
方法, 最終呼叫registerDispatcherServlet()
方法完成註冊, 兩個方法的程式碼如下:
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
super.onStartup(servletContext);
registerDispatcherServlet(servletContext);
}
protected void registerDispatcherServlet(ServletContext servletContext) {
// 獲取Sevlet名稱, 這個方法返回了預設值"dispatcher"
String servletName = getServletName();
Assert.hasLength(servletName, "getServletName() must not return null or empty");
// 此處呼叫的方法是抽象方法, 由子類AbstractAnnotationConfigDispatcherServletInitializer實現, 其最終呼叫了自定義類的getServletConfigClasses()方法獲取配置資訊(原始碼附在本段後面). 用來生成Servlet上下文.
WebApplicationContext servletAppContext = createServletApplicationContext();
Assert.notNull(servletAppContext, "createServletApplicationContext() must not return null");
// 生成dispatcherServlet例項
FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
Assert.notNull(dispatcherServlet, "createDispatcherServlet(WebApplicationContext) must not return null");
dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());
// 註冊DispatcherServlet
ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
if (registration == null) {
throw new IllegalStateException("Failed to register servlet with name '" + servletName + "'. " +
"Check if there is another servlet registered under the same name.");
}
registration.setLoadOnStartup(1);
registration.addMapping(getServletMappings());
registration.setAsyncSupported(isAsyncSupported());
Filter[] filters = getServletFilters();
if (!ObjectUtils.isEmpty(filters)) {
for (Filter filter : filters) {
registerServletFilter(servletContext, filter);
}
}
customizeRegistration(registration);
}
下面附讀取Servlet配置類的程式碼: 類AbstractAnnotationConfigDispatcherServletInitializer
實現了createServletApplicationContext()
, 可以看到程式碼中呼叫了方法getServletConfigClasses()
, 這是個抽象方法, 宣告為protected abstract Class<?>[] getServletConfigClasses();
. 最終的實現正是在我們自定義的子類MvcWebAppInitializer
中.
@Override
protected WebApplicationContext createServletApplicationContext() {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
// 讀取配置類
Class<?>[] configClasses = getServletConfigClasses();
if (!ObjectUtils.isEmpty(configClasses)) {
context.register(configClasses);
}
return context;
}
上面完成了DispatcherServlet的註冊和啟動, 接下來可以定義Controller了.
請求對映
在此之前需要了解下關於URL對映的Servlet規範, 注意這是Servlet的規範, 當然也適用於DispatcherServlet, 程式碼中我們為DispatcherServlet對映為"/", 規範中"/"為使用"default"Servlet, 也就意味著所有的請求預設通過DispatcherServlet處理.
為了處理靜態資源, 在WebConfig
中覆蓋了方法configureDefaultServletHandling()
已啟用靜態資源處理器DefaultServletHttpRequestHandler
, 它的優先順序是最低, 這意味著在匹配不到其他handler的時候,servlet會將請求交給這個handler處理.
規則按順序執行,匹配到就直接返回.
- 精確匹配, url完全與模式匹配
- 最長路徑匹配, 查詢模式中路徑最長的匹配項, 例如/user/list/1匹配模式/user/list/, 而不是/user/
- 副檔名匹配
- 預設Servlet
程式碼
@Controller
@RequestMapping(value = "/home")
public class HomeController {
@RequestMapping(value = "/default",method = RequestMethod.GET)
public String home(){
return "home";
}
}
原始碼分析
我們的Controller以註解(@RequestMapping
,@GetMapping
等)方式定義, RequestMappingHandlerMapping
用來生成請求url與處理方法的對映關係(mapping),這個mapping最終是由DispatcherServlet呼叫找到匹配到url對應的controller方法並呼叫.
通過檢視Spring的bean依賴關係圖(找到類WebConfig
, Ctrl
+Alt
+U
並選spring beans dependency)可以找到RequestMappingHandlerMapping
生成的線索.
簡化的關係圖如下:
可以看到WebmvcConfigurationSupport
中有個@Bean
註解的方法生成RequestMappingHandlerMapping
的例項, 而WebmvcConfigurationSupport
繼承了DelegatingWebMvcConfiguration
, 後者是由@EnableWebMvc
註解匯入.
/**
*
* 返回排序為0的RequestMappingHandlerMapping例項bean, 用來處理註解方式的Controller請求.
*/
@Bean
public RequestMappingHandlerMapping requestMappingHandlerMapping() {
RequestMappingHandlerMapping mapping = createRequestMappingHandlerMapping();
// 順序為0, 順便提一句, 靜態資源的處理器Handler的順序為Integer.Max
mapping.setOrder(0);
mapping.setInterceptors(getInterceptors());
mapping.setContentNegotiationManager(mvcContentNegotiationManager());
mapping.setCorsConfigurations(getCorsConfigurations());
PathMatchConfigurer configurer = getPathMatchConfigurer();
Boolean useSuffixPatternMatch = configurer.isUseSuffixPatternMatch();
if (useSuffixPatternMatch != null) {
mapping.setUseSuffixPatternMatch(useSuffixPatternMatch);
}
Boolean useRegisteredSuffixPatternMatch = configurer.isUseRegisteredSuffixPatternMatch();
if (useRegisteredSuffixPatternMatch != null) {
mapping.setUseRegisteredSuffixPatternMatch(useRegisteredSuffixPatternMatch);
}
Boolean useTrailingSlashMatch = configurer.isUseTrailingSlashMatch();
if (useTrailingSlashMatch != null) {
mapping.setUseTrailingSlashMatch(useTrailingSlashMatch);
}
UrlPathHelper pathHelper = configurer.getUrlPathHelper();
if (pathHelper != null) {
mapping.setUrlPathHelper(pathHelper);
}
PathMatcher pathMatcher = configurer.getPathMatcher();
if (pathMatcher != null) {
mapping.setPathMatcher(pathMatcher);
}
Map<String, Predicate<Class<?>>> pathPrefixes = configurer.getPathPrefixes();
if (pathPrefixes != null) {
mapping.setPathPrefixes(pathPrefixes);
}
return mapping;
}
好了, 現在有了DispatcherServlet, 並且有了可以處理對映關係的RequestMappingHandlerMapping, 接下來再看下當請求到達時, DispatcherServlet 如何為Url找到對應的Handler方法.
DispatcherServlet
中定義了處理請求的doService()
方法, 最終這個方法委託doDispatch()
處理請求, 特別注意中文註釋的幾個語句, 除此之外, 這個方法還提供了生命週期的一些處理工作.
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// 獲取當前請求對應的handler
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// 獲取當前請求對應handler的介面卡
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 最終呼叫Handler的方法
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
上面程式碼中, 重點關注getHandler
方法.
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
for (HandlerMapping mapping : this.handlerMappings) {
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
可以看到請求所需的handler是取自例項變數this.handlerMappings
,接下來順藤摸瓜, 看這個變數是何時初始化的.通過引用, 我們查詢到了下面方法.
private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null;
if (this.detectAllHandlerMappings) {
// 找到上下文中的所有HandlerMapping, 包括祖先上下文
Map<String, HandlerMapping> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerMappings = new ArrayList<>(matchingBeans.values());
// HandlerMapping排序
AnnotationAwareOrderComparator.sort(this.handlerMappings);
}
}
else {
try {
HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
this.handlerMappings = Collections.singletonList(hm);
}
catch (NoSuchBeanDefinitionException ex) {
// Ignore, we'll add a default HandlerMapping later.
// 這個註釋...
}
}
// 保證至少要有一個HandlerMapping.
if (this.handlerMappings == null) {
this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
if (logger.isTraceEnabled()) {
logger.trace("No HandlerMappings declared for servlet '" + getServletName() +
"': using default strategies from DispatcherServlet.properties");
}
}
}
整理下呼叫關係: DispatcherServlet
initHandlerMappings <-- initStrategies <-- onRefresh <--
FrameworkServlet
initWebApplicationContext <-- initServletBean <--
HttpServletBean
init <--
GenericServlet
init(ServletConfig config)
最後的GenericServlet
是servlet Api的.
Spring Boot 中的DispatcherServlet
Spring Boot微服務中的DispatcherServlet裝配, 因為其一般使用內建的Servlet容器, 是通過DispatcherServletAutoConfiguration
來完成的. 下面是生成DispatcherServlet bean的程式碼, 這個bean在內部靜態類DispatcherServletConfiguration
中.
@Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public DispatcherServlet dispatcherServlet() {
DispatcherServlet dispatcherServlet = new DispatcherServlet();
dispatcherServlet.setDispatchOptionsRequest(
this.webMvcProperties.isDispatchOptionsRequest());
dispatcherServlet.setDispatchTraceRequest(
this.webMvcProperties.isDispatchTraceRequest());
dispatcherServlet.setThrowExceptionIfNoHandlerFound(
this.webMvcProperties.isThrowExceptionIfNoHandlerFound());
return dispatcherServlet;
}
上面我們通過註解方式構建了一個MVC應用程式, 並且通過原始碼分析其構建原理, 其中Spring使用的前端控制器實現類是DispatcherServlet
, 其在Servlet容器啟動的時候例項化, 並初始化容器中的Handler處理器. 當請求到達DispatcherServlet
時會呼叫其doDispatcher()
方法選擇最合適的處理器. 最後我們掃了一眼Spring Boot的自動裝配DispatcherServlet
方式.