文末可以領取所有系列高清 pdf,
大家好,我是路人,這是 SpringMVC 系列第 17 篇,
本文將通過閱讀原始碼的方式帶大家了解 springmv 容器啟動的程序,SpringMVC 中的各種組件都是在容器啟動的程序中組裝的,所以如果熟悉整個程序后,你可以隨意對 SpringMVC 進行擴展,SpringMVC 會被你玩的出神入化,
目錄
1、前言
2、回顧全注解方式 2 個關鍵類
2.1、MVC 初始化類
2.2、MVC 配置類
2、SpringMVC 容器的生命周期(9 個階段)
3、階段 1:Servlet 容器初始化
3.1、ServletContainerInitializer
3.2、SpringServletContainerInitializer
3.3、SpringServletContainerInitializer#onStartup 方法
3.4、WebApplicationInitializer:web 應用初始化
3.5、進入 AbstractDispatcherServletInitializer#onStartup 方法
4、階段 2:創建父容器
4.1、程序
4.2、①:負責創建父容器
4.2、②:創建 ContextLoaderListener 監聽器
5、階段 3&4:創建 springmvc 容器&注冊 DispatcherServlet
6、階段 5:啟動父容器:ContextLoaderListener
6.1、程序
6.2、代碼 ①:配置父容器以及啟動父容器
6.3、ApplicationContextInitializer 介面:容器啟動前用來初始化容器
7、階段 6:啟動 springmvc 容器:DispatcherServlet#init()
7.1、DispatcherServlet 類圖
7.2、HttpServletBean#init()
7.3、FrameworkServlet#initServletBean
7.4、FrameworkServlet#initWebApplicationContext
7.5、FrameworkServlet#configureAndRefreshWebApplicationContext
8、階段 7:springmvc 容器啟動程序中處理@WebMVC
8.1、SpringMVC 配置類被處理
8.2、@EnableWebMvc:配置 springmvc 所需組件
8.3、進入 DelegatingWebMvcConfiguration 類
8.4、WebMvcConfigurationSupport:配置 springmvc 所需所有組件
8.5、WebMvcConfigurer 介面
9、階段 8:組裝 DispatcherServlet 中各種 SpringMVC 需要的組件
9.1、觸發 ContextRefreshListener 監聽器
9.2、進入 FrameworkServlet.this.onApplicationEvent(event);
9.3、進入 DispatcherServlet#onRefresh
9.4、DispatcherServlet#initStrategies:初始化 DispatcherServlet 中的組件
9.5、initHandlerMappings(context);
9.6、DispatcherServlet#getDefaultStrategies:兜底的方案查找組件
10、階段 9:銷毀容器
10.1、銷毀 springmvc 容器:DispatcherServlet#destroy
10.2、銷毀父容器:ContextLoaderListener#contextDestroyed
11、帶大家 debug 代碼
11.1、拉取原始碼
11.2、將下面這個模塊發布到 tomcat
11.2、按照下面配置設定斷點,啟動,除錯代碼
12、SpringMVC 系列目錄
13、更多好文章
14、【路人甲 Java】所有系列高清 PDF
1、前言
SpringMVC,建議大家使用全注解的方式,全注解的方式及原理不了解的,先去看一下這篇文章:SpringMVC 全注解方式如何使用?
上面的文章看懂以后再來看本文,否則將出現消化不良的現象,
本文以全注解的方式為基礎,來解說SpringMVC容器啟動的整個流程,
2、回顧全注解方式 2 個關鍵類
全注解的方式重點就在于 2 個類:MVC 初始化類、MVC 配置類
2.1、MVC 初始化類
代碼如下,這個類需要繼承 AbstractAnnotationConfigDispatcherServletInitializer,會有 web 容器來呼叫,這個類中有 4 個方法需要實作,干了 4 件事情
getRootConfigClasses():獲取父容器的配置類
getServletConfigClasses():獲取 springmvc 容器的配置類,這個配置類相當于 springmvc xml 組態檔的功能
getServletMappings():獲取 DispatcherServlet 能夠處理的 url,相當于 web.xml 中為 servlet 指定的 url-pattern
getServletFilters():定義所有的 Filter
/**
* ①:1、創建Mvc初始化類,需要繼承AbstractAnnotationConfigDispatcherServletInitializer類
*/
public class MvcInit extends AbstractAnnotationConfigDispatcherServletInitializer {
/**
* springmvc容器的父容器spring配置類
* 實際作業中我們的專案比較復雜,可以將controller層放在springmvc容器中
* 其他層,如service層、dao層放在父容器了,bean管理起來更清晰一些
* 也可以沒有父容器,將所有bean都放在springmvc容器中
*
* @return
*/
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[0];
}
/**
* ②:2、設定springmvc容器的spring配置類
*
* @return
*/
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{MvcConfig.class};
}
/**
* ③:3、配置DispatcherServlet的url-pattern
*
* @return
*/
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
/**
* ④:4、注冊攔截器
*
* @return
*/
@Override
protected Filter[] getServletFilters() {
//添加攔截器,解決亂碼問題
CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
characterEncodingFilter.setEncoding("UTF-8");
characterEncodingFilter.setForceRequestEncoding(true);
characterEncodingFilter.setForceResponseEncoding(true);
return new Filter[]{characterEncodingFilter};
}
}
2.2、MVC 配置類
代碼如下,這個配置類相當于 springmvc xml 組態檔的功能,可以在里面定義 springmvc 各種組件
/**
* 1.開啟springmvc注解配置
* 2、配置視圖決議器
* 3、配置截器
* 4、配置靜態資源訪問
* 5、組態檔上傳決議器
* 6、配置全域例外處理器
*/
@Configuration
@ComponentScan("com.javacode2018.springmvc.chat12")
@EnableWebMvc //1:使用EnableWebMvc開啟springmvc注解方式配置
public class MvcConfig implements WebMvcConfigurer {
/**
* 2、添加視圖決議器(可以添加多個)
*
* @param registry
*/
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/view/");
resolver.setSuffix(".jsp");
resolver.setOrder(Ordered.LOWEST_PRECEDENCE);
registry.viewResolver(resolver);
}
@Autowired
private MyInterceptor myInterceptor;
/**
* 3、添加攔截器(可以添加多個)
*
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(this.myInterceptor).addPathPatterns("/**");
}
/**
* 4、配置靜態資源訪問處理器
*
* @param registry
*/
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/**").addResourceLocations("/static/");
}
/**
* 5、組態檔上傳決議器
*
* @return
*/
@Bean
public CommonsMultipartResolver multipartResolver() {
CommonsMultipartResolver commonsMultipartResolver = new CommonsMultipartResolver();
//maxUploadSizePerFile:單個檔案大小限制(byte)
//maxUploadSize:整個請求大小限制(byte)
commonsMultipartResolver.setMaxUploadSizePerFile(10 * 1024 * 1024);
commonsMultipartResolver.setMaxUploadSize(100 * 1024 * 1024);
return commonsMultipartResolver;
}
}
2、SpringMVC 容器的生命周期(9 個階段)
階段 1:Servlet 容器初始化
階段 2:創建父容器
階段 3:創建 springmvc 容器
階段 4:Servlet 容器中注冊 DispatcherServlet
階段 5:啟動父容器:ContextLoaderListener
階段 6:啟動 springmvc 容器:DispatcherServlet#init()
階段 7:springmvc 容器啟動程序中處理@WebMVC
階段 8:組裝 DispatcherServlet 中各種 SpringMVC 需要的組件
階段 9:銷毀 2 個容器
3、階段 1:Servlet 容器初始化
3.1、ServletContainerInitializer
咱們知道 servlet3.0 中新增了一個介面:ServletContainerInitializer,這個介面功能特別的牛逼,有了它之后,web.xml 組態檔可要可不要了,
public interface ServletContainerInitializer {
public void onStartup(Set<Class<?>> c, ServletContext ctx)
throws ServletException;
}
這個介面的實作類,如果滿足下面 2 個條件,Servlet 容器啟動的程序中會自動實體化這些類,然后呼叫他們的 onStartUp 方法,然后我們就可以在這些類的 onStartUp 方法中干活了,在 web.xml 干的所有事情,都可以在這個方法中干,特別強大:
這個類必須實作 ServletContainerInitializer 介面,且非抽象類
這個類的全類名必須要放在
META-INF/services/javax.servlet.ServletContainerInitializer這個檔案中
3.2、SpringServletContainerInitializer
下面重點來了,springmvc 提供了一個類SpringServletContainerInitializer,滿足了上面個條件,

spring-web-5.3.6.jar!\META-INF\services\javax.servlet.ServletContainerInitializer

所以 SpringServletContainerInitializer 的 onStart 方法會 servlet 容器自動被呼叫
3.3、SpringServletContainerInitializer#onStartup 方法
這個類的原始碼,大家先看一下,這個類干的事情:
類上有@HandlesTypes(WebApplicationInitializer.class) 這個注解,注解的值為
WebApplicationInitializer.class,所以 onStartup 方法的第一個引數是WebApplicationInitializer型別的集合,這個集合由 web 容器自動掃描獲取,然后傳入進來實體化 WebApplicationInitializer 集合
對 WebApplicationInitializer 集合進行排序
回圈呼叫 WebApplicationInitializer 的 onStartup 方法
@HandlesTypes(WebApplicationInitializer.class) //@1
public class SpringServletContainerInitializer implements ServletContainerInitializer {
@Override
public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
throws ServletException {
List<WebApplicationInitializer> initializers = Collections.emptyList();
if (webAppInitializerClasses != null) {
initializers = new ArrayList<>(webAppInitializerClasses.size());
for (Class<?> waiClass : webAppInitializerClasses) {
// Be defensive: Some servlet containers provide us with invalid classes,
// no matter what @HandlesTypes says...
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);
for (WebApplicationInitializer initializer : initializers) {
initializer.onStartup(servletContext);
}
}
}
下面重點要看WebApplicationInitializer介面了,
3.4、WebApplicationInitializer:web 應用初始化
介面比較簡單,就一個方法,引數是 servlet 背景關系物件,有了個物件,可以干 web.xml 中的一切事情了,比如注冊 servlet、filter、監聽器等等
public interface WebApplicationInitializer {
void onStartup(ServletContext servletContext) throws ServletException;
}
如下圖,看一下類的繼承關系,咱們的 MvcInit 就實作了這個介面,所以 MvcInit 的 onStartup 方法會被調費用

關鍵代碼在這 3 個類中

3.5、進入 AbstractDispatcherServletInitializer#onStartup 方法
public void onStartup(ServletContext servletContext) throws ServletException {
super.onStartup(servletContext);
registerDispatcherServlet(servletContext);
}
這里是重點:這個方法中干了 4 件事情
創建父容器,只是實體化了,并未啟動
創建了監聽器 ContextLoaderListener,這是一個 ServletContextListener 型別的監聽器,稍后會在這個監聽器中啟動父容器
創建 springmvc 容器,只是實體化了,并未啟動,啟動的事情會在 DispatcherServlet#init 中做,稍后會說
Servlet 容器中注冊 DispatcherServlet
下面,咱們來詳細看這幾個步驟,把這幾個步驟作為階段來解讀,
4、階段 2:創建父容器
父容器可有可無,并不是必須的,為了更好的管理 bean,springmvc 建議我們用父子容器,controller 之外的 bean,比如 service,dao 等,建議放到父容器中,controller 層的和 springmvc 相關的一些 bean 放在 springmvc 容器中,咱們繼續,
4.1、程序
AbstractDispatcherServletInitializer#onStartup方法中會呼叫父類的onStartup,即AbstractContextLoaderInitializer#onStartup,我們進到這個方法中,代碼如下圖,干了 2 個事情
圖中編號 ①:創建父容器,只是實體化了,并未啟動
圖中編號 ②:創建了一個監聽器 ContextLoaderListener,這是一個 ServletContextListener 型別的監聽器,稍后會在這個監聽器中啟動父容器

下面來分別來細說下上面 2 段代碼干的活,
4.2、①:負責創建父容器
AbstractAnnotationConfigDispatcherServletInitializer#createRootApplicationContext,只是創建了一個AnnotationConfigWebApplicationContext物件,并將父容器配置類 rootConfigClass 注冊到容器中,并沒有啟動這個容器,若 rootConfigClass 為空,父容器不會被創建,所以父容器可有可無,

4.2、②:創建 ContextLoaderListener 監聽器
代碼如下,創建的時候將父容器物件 rootAContext 傳進去了,
ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
//getRootApplicationContextInitializers()回傳置為ApplicationContextInitializer陣列,是個函式式介面,在父容器初始化的程序中,會作為一個擴展點預留給開發者用
listener.setContextInitializers(getRootApplicationContextInitializers());
servletContext.addListener(listener);
ContextLoaderListener,這是一個 ServletContextListener 型別的監聽器,所以在 web 容器啟動和銷毀的程序中會被呼叫,如下圖,這個監聽器干了 2 件事
contextInitialized 方法:這個方法會在 web 容器啟動時被呼叫,內部負責啟動父容器
在 contextDestroyed 方法:這個方法會在 web 容器銷毀時被呼叫,內部負責關閉父容器

5、階段 3&4:創建 springmvc 容器&注冊 DispatcherServlet
在回到AbstractDispatcherServletInitializer#onStartup,看這個方法的第二行,如下圖

registerDispatcherServlet原始碼如下
protected void registerDispatcherServlet(ServletContext servletContext) {
//①:DispatcherServlet的servlet名稱,默認為:dispatcher
String servletName = getServletName();
//②:創建springmvc容器
WebApplicationContext servletAppContext = createServletApplicationContext();
//③:創建DispatcherServlet,注意這里將springmvc容器物件做為引數傳遞給DispatcherServlet了
FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
//設定ApplicationContextInitializer串列,可以對springmvc容器在啟動之前進行定制化
dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());
//④:將 dispatcherServlet 注冊到servlet背景關系中
ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
registration.setLoadOnStartup(1);
registration.addMapping(getServletMappings());
registration.setAsyncSupported(isAsyncSupported());
//⑤:注冊Filter
Filter[] filters = getServletFilters();
if (!ObjectUtils.isEmpty(filters)) {
for (Filter filter : filters) {
registerServletFilter(servletContext, filter);
}
}
//⑥:這個方法預留給咱們自己去實作,可以對dispatcherServlet做一些特殊的配置
customizeRegistration(registration);
}
protected FrameworkServlet createDispatcherServlet(WebApplicationContext servletAppContext) {
return new DispatcherServlet(servletAppContext);
}
6、階段 5:啟動父容器:ContextLoaderListener
6.1、程序
上面的onStartup方法執行完畢之后,會執行監聽器ContextLoaderListener的初始化,會進入到他的contextInitialized方法中

initWebApplicationContext原始碼如下,截取了主要的幾行
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
//this.context就是父容器物件
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
//①:配置及啟動父容器
configureAndRefreshWebApplicationContext(cwac, servletContext);
//將父容器丟到servletContext中進行共享,方便其他地方獲取
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
}
6.2、代碼 ①:配置父容器以及啟動父容器
//①:配置及啟動父容器
configureAndRefreshWebApplicationContext(cwac, servletContext);
configureAndRefreshWebApplicationContext方法關鍵代碼如下
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
//①:定制上線文,這里主要是遍歷ApplicationContextInitializer串列,呼叫每個ApplicationContextInitializer#initialize方法來對容器進行定制,相當于一個擴展點,可以有程式員自己控制
customizeContext(sc, wac);
//②:重繪容器,就相當于啟動容器了,此時就會組裝里面的bean了
wac.refresh();
}
customizeContext方法,我們進去看一下,這里涉及到了一個新的類,所以有必要去看一下,混個臉熟,原始碼如下,這是給開發者留的一個擴展點,通過ApplicationContextInitializer這個來做擴展,這是一個函式式介面,下面代碼會遍歷ApplicationContextInitializer串列,然后呼叫其initialize方法,我們可以在這個方法中對 spring 上線文進行定制
protected void customizeContext(ServletContext sc, ConfigurableWebApplicationContext wac) {
List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> initializerClasses =
determineContextInitializerClasses(sc);
for (Class<ApplicationContextInitializer<ConfigurableApplicationContext>> initializerClass : initializerClasses) {
Class<?> initializerContextClass =
GenericTypeResolver.resolveTypeArgument(initializerClass, ApplicationContextInitializer.class);
if (initializerContextClass != null && !initializerContextClass.isInstance(wac)) {
throw new ApplicationContextException(String.format(
"Could not apply context initializer [%s] since its generic parameter [%s] " +
"is not assignable from the type of application context used by this " +
"context loader: [%s]", initializerClass.getName(), initializerContextClass.getName(),
wac.getClass().getName()));
}
this.contextInitializers.add(BeanUtils.instantiateClass(initializerClass));
}
AnnotationAwareOrderComparator.sort(this.contextInitializers);
for (ApplicationContextInitializer<ConfigurableApplicationContext> initializer : this.contextInitializers) {
initializer.initialize(wac);
}
}
6.3、ApplicationContextInitializer 介面:容器啟動前用來初始化容器
是個函式式介面,在容器啟動之前用來對容器進行定制,作為一個擴展點預留給開發者用,父容器和 springmvc 容器都用到了,
@FunctionalInterface
public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {
/**
* 初始化給定的spring容器
* @param applicationContext the application to configure
*/
void initialize(C applicationContext);
}
7、階段 6:啟動 springmvc 容器:DispatcherServlet#init()
到目前為止父容器已經啟動完畢了,此時 DispatcherServlet 會被初始化,會進入到他的 init()方法中,
7.1、DispatcherServlet 類圖

7.2、HttpServletBean#init()
這個方法會呼叫
initServletBean()這個方法,其他的先不看

7.3、FrameworkServlet#initServletBean
提取了關鍵的代碼,就 2 行
@Override
protected final void initServletBean() throws ServletException {
//初始化springmvc容器,就是啟動springmvc容器
this.webApplicationContext = initWebApplicationContext();
//這個方法內部是空的,預留給子類去實作的,目前沒啥用
initFrameworkServlet();
}
下面咱們進到initWebApplicationContext方法中去,
7.4、FrameworkServlet#initWebApplicationContext
關鍵代碼如下,干了 3 件事情:
從 servlet 上線文物件中找到父容器
為 springmvc 容器指定父容器
呼叫 configureAndRefreshWebApplicationContext 方法配置 springmvc 容器以及啟動容器,這個是關鍵咯
protected WebApplicationContext initWebApplicationContext() {
//①:從servlet上線文中獲取父容器
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
//②:this.webApplicationContext就是springmvc容器,此時這個對物件不為null,所以滿足條件
if (this.webApplicationContext != null) {
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
//springmvc容器未啟動
if (!cwac.isActive()) {
//springmvc容器未設定父容器,則給其設定父容器,此時rootContext可能為空,這個沒什么關系
if (cwac.getParent() == null) {
cwac.setParent(rootContext);
}
//③:配置springmvc容器以及啟動springmvc容器
configureAndRefreshWebApplicationContext(cwac);
}
}
}
//這里省略了一部分代碼,如果springmvc采用組態檔的方式會走這部分代碼
......
//回傳容器
return wac;
}
7.5、FrameworkServlet#configureAndRefreshWebApplicationContext
為了讓大家看清楚,如下代碼,這里只提取了關鍵代碼,主要干了 3 件事情
代碼 ①:向 springmvc 容器中添加了一個 ContextRefreshListener 監聽器,這個監聽器非常非常重要,springmvc 容器啟動完畢之后會被呼叫,會出現在階段 8 中
代碼 ②:給開發者預留的一個擴展點,通過 ApplicationContextInitializer#initialize 方法對容器進行定制
代碼 ③:啟動容器
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
//①:向springmvc容器中添加了一個監聽器物件,這個監聽器特別重要,稍后在
wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
//②:擴展點:回圈遍歷ApplicationContextInitializer串列,呼叫其initialize方法對容器進行定制
applyInitializers(wac);
//③:重繪容器,相當于啟動容器
wac.refresh();
}
8、階段 7:springmvc 容器啟動程序中處理@WebMVC
8.1、SpringMVC 配置類被處理
此時 springmvc 容器啟動了,此時注意下MvcConfig這個類,由于其上有@Conguration 注解,所以會被當做一個配置類被處理,這個類有 2 個非常重要的特征,
標注了@EnableWebMvc 注解
實作了 WebMvcConfigurer 介面

下面來說說這 2 個特征的作用,
8.2、@EnableWebMvc:配置 springmvc 所需組件
看一下這個注解的原始碼,如下,重點在于它上面的@Import(DelegatingWebMvcConfiguration.class)注解,這個注解的功能不知道的,可以回頭去看我的 spring 系列,從頭看一遍,

8.3、進入 DelegatingWebMvcConfiguration 類
代碼如下,先注意下面 3 個特征
代碼編號 ①:標注有@Configuration 注解,說明是一個配置類
代碼編號 ②:繼承了 WebMvcConfigurationSupport 類,這個類中有很多@Bean 標注的方法,用來定義了 springmvc 需要的所有組件
代碼編號 ③:注入了
WebMvcConfigurer串列,注意下,我們的 WebConfig 類就實作了 WebMvcConfigurer 這個介面,內部提供了很多方法可以用來對 springmvc 的組件進行自定義配置

先來看看 WebMvcConfigurationSupport 這個類,
8.4、WebMvcConfigurationSupport:配置 springmvc 所需所有組件
這個類中會定義 springmvc 需要的所有組件,比如:RequestMapping、HandlerAdapter、HandlerInterceptor、HttpMessageConverter、HandlerMethodArgumentResolver、HandlerMethodReturnValueHandler 等等,所以如果你感覺@WebMVC 注解滿足不了你的需求時,你可以直接繼承這個類進行擴展,
這個類的原始碼我就不貼了,截幾個圖給大家看看

8.5、WebMvcConfigurer 介面
這個介面就是我們用來對 springmvc 容器中的組件進行定制的,WebMvcConfigurationSupport 中創建 springmvc 組件的時候,會自動呼叫 WebMvcConfigurer 中對應的一些方法,來對組件進行定制,比如可以在 WebMvcConfigurer 中添加攔截器、配置默認 servlet 處理器、靜態資源處理器等等,這個介面的原始碼如下
public interface WebMvcConfigurer {
/**
* 配置PathMatchConfigurer
*/
default void configurePathMatch(PathMatchConfigurer configurer) {
}
/**
* 配置ContentNegotiationConfigurer
*/
default void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
}
/**
* 異步處理配置
*/
default void configureAsyncSupport(AsyncSupportConfigurer configurer) {
}
/**
* 配置默認servlet處理器
*/
default void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
}
/**
* 配置Formatter
*/
default void addFormatters(FormatterRegistry registry) {
}
/**
* 添加攔截器
*/
default void addInterceptors(InterceptorRegistry registry) {
}
/**
* 靜態資源配置
*/
default void addResourceHandlers(ResourceHandlerRegistry registry) {
}
/**
* 跨越的配置
*/
default void addCorsMappings(CorsRegistry registry) {
}
/**
* 配置ViewController
*/
default void addViewControllers(ViewControllerRegistry registry) {
}
/**
* 注冊視圖決議器(ViewResolverRegistry)
*/
default void configureViewResolvers(ViewResolverRegistry registry) {
}
/**
* 注冊處理器方法引數決議器(HandlerMethodArgumentResolver)
*/
default void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
}
/**
* 注冊處理器方法回傳值處理器(HandlerMethodReturnValueHandler)
*/
default void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> handlers) {
}
/**
* 注冊http報文轉換器(HttpMessageConverter)
*/
default void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
}
/**
* 擴展報文轉換器
*/
default void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
}
/**
* 配置例外決議器(HandlerExceptionResolver)
*/
default void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
}
/**
* 擴展例外決議器(HandlerExceptionResolver)
*/
default void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
}
/**
* 獲得驗證器
*/
@Nullable
default Validator getValidator() {
return null;
}
/**
* 獲得MessageCodesResolver
*/
@Nullable
default MessageCodesResolver getMessageCodesResolver() {
return null;
}
}
9、階段 8:組裝 DispatcherServlet 中各種 SpringMVC 需要的組件
9.1、觸發 ContextRefreshListener 監聽器
大家回頭看一下 8.5 中,有這樣一段代碼,注冊了一個監聽器ContextRefreshListener

再來看看這個監聽器的原始碼,如下圖,包含 2 點資訊
會監聽 ContextRefreshedEvent 事件
監聽到事件之后將執行
FrameworkServlet.this.onApplicationEvent(event);,稍后會具體說這個代碼

如下代碼,springmvc 容器啟動完畢之后,會發布一個ContextRefreshedEvent事件,會觸發上面這個監聽器的執行

9.2、進入 FrameworkServlet.this.onApplicationEvent(event);
public void onApplicationEvent(ContextRefreshedEvent event) {
//標記已收到重繪事件
this.refreshEventReceived = true;
synchronized (this.onRefreshMonitor) {
onRefresh(event.getApplicationContext());
}
}
上面的onRefresh(event.getApplicationContext());會進到DispatcherServlet#onRefresh方法中,
9.3、進入 DispatcherServlet#onRefresh
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
里面會呼叫initStrategies方法,
9.4、DispatcherServlet#initStrategies:初始化 DispatcherServlet 中的組件
代碼如下,這里面會初始化 DispatcherServlet 中的各種組件,這里的所有方法初始化的程序基本上差不多,就是先從 springmvc 容器中找這個組件,如果找不到一般會有一個兜底的方案
protected void initStrategies(ApplicationContext context) {
//初始化MultipartResolver
initMultipartResolver(context);
//初始化LocaleResolver
initLocaleResolver(context);
//初始化ThemeResolver
initThemeResolver(context);
//初始化HandlerMappings
initHandlerMappings(context);
//初始化HandlerAdapters
initHandlerAdapters(context);
//初始化HandlerExceptionResolvers
initHandlerExceptionResolvers(context);
//初始化RequestToViewNameTranslator
initRequestToViewNameTranslator(context);
//初始化視圖決議器ViewResolvers
initViewResolvers(context);
//初始化FlashMapManager
initFlashMapManager(context);
}
下面我們以initHandlerMappings(context);為例來看一下是如何初始化這些組件的,
9.5、initHandlerMappings(context);
原始碼如下,就是先從容器中找,找不到走兜底的方案,
private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null;
//是否需要查找所有的HandlerMapping,默認為true
if (this.detectAllHandlerMappings) {
//從容器中查找所有的HandlerMapping
Map<String, HandlerMapping> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
//對HandlerMapping串列進行排序
if (!matchingBeans.isEmpty()) {
this.handlerMappings = new ArrayList<>(matchingBeans.values());
AnnotationAwareOrderComparator.sort(this.handlerMappings);
}
}
else {
try {
//查找名稱為handlerMapping的HandlerMapping
HandlerMapping hm = context.getBean("handlerMapping", HandlerMapping.class);
this.handlerMappings = Collections.singletonList(hm);
}
catch (NoSuchBeanDefinitionException ex) {
}
}
// 如果沒有找到HandlerMapping,則走兜底的方案
if (this.handlerMappings == null) {
this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
}
}
下面我們來看一下兜底的代碼如何走的,進入 getDefaultStrategies 方法
9.6、DispatcherServlet#getDefaultStrategies:兜底的方案查找組件
這個方法的原始碼就不貼出來了,這里只說一下兜底的處理程序,springmvc 有個組態檔:spring-webmvc-5.3.6.jar!\org\springframework\web\servlet\DispatcherServlet.properties,properties 格式的檔案,key 為組件的完整類名,value 為多個實作類的串列,在這個組態檔中指定了每種型別的組件兜底的情況下對應的實作類,比如沒有找到 RequestMapping 的情況下,如下圖紅框的部分,有 3 個兜底的實作類,然后 springmvc 會實體化這 3 個類作為 RequestMapping,

10、階段 9:銷毀容器
10.1、銷毀 springmvc 容器:DispatcherServlet#destroy
DispatcherServlet 銷毀的時候會關閉 springmvc 容器
public void destroy() {
if (this.webApplicationContext instanceof ConfigurableApplicationContext && !this.webApplicationContextInjected) {
((ConfigurableApplicationContext) this.webApplicationContext).close();
}
}
10.2、銷毀父容器:ContextLoaderListener#contextDestroyed
父容器是在監聽器中啟動的,所以銷毀的也是監聽器負責的
public void contextDestroyed(ServletContextEvent event) {
closeWebApplicationContext(event.getServletContext());
ContextCleanupListener.cleanupAttributes(event.getServletContext());
}
springmvc 容器的生命周期到此就結束了,想掌握好這個程序,建議大家 debug 走幾遍,就熟悉了,下面帶大家 debug 一下代碼,
11、帶大家 debug 代碼
11.1、拉取原始碼
https://gitee.com/javacode2018/springmvc-series
11.2、將下面這個模塊發布到 tomcat

11.2、按照下面配置設定斷點,啟動,除錯代碼
依次在下面這些方法中設定斷點,然后啟動 tomcat,一步步除錯,我相信你們肯定可以吃透的,
1、org.springframework.web.SpringServletContainerInitializer#onStartup:入口
2、org.springframework.web.servlet.support.AbstractDispatcherServletInitializer#onStartup
3、org.springframework.web.context.AbstractContextLoaderInitializer#onStartup
4、org.springframework.web.context.AbstractContextLoaderInitializer#registerContextLoaderListener:創建父容器
5、org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer#createRootApplicationContext
6、org.springframework.web.servlet.support.AbstractDispatcherServletInitializer#registerDispatcherServlet
7、org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer#createServletApplicationContext:創建springmvc容器 & 注冊DispatcherServlet
8、org.springframework.web.context.ContextLoaderListener#contextInitialized
9、org.springframework.web.context.ContextLoader#initWebApplicationContext
10、org.springframework.web.context.ContextLoader#configureAndRefreshWebApplicationContext:啟動父容器
11、org.springframework.web.servlet.HttpServletBean#init
12、org.springframework.web.servlet.FrameworkServlet#initServletBean
13、org.springframework.web.servlet.FrameworkServlet#initWebApplicationContext:初始化springmvc容器&啟動springmvc容器
14、org.springframework.web.servlet.FrameworkServlet#configureAndRefreshWebApplicationContext:啟動springmvc容器
15、org.springframework.web.servlet.FrameworkServlet.ContextRefreshListener#onApplicationEvent
16、org.springframework.web.servlet.DispatcherServlet#onRefresh
17、org.springframework.web.servlet.DispatcherServlet#initStrategies:組裝Dispathcer中各種springmvc組件
有問題歡迎加我微信:itsoku,交流,
12、SpringMVC 系列目錄
SpringMVC 系列第 1 篇:helloword
SpringMVC 系列第 2 篇:@Controller、@RequestMapping
SpringMVC 系列第 3 篇:例外高效的一款介面測驗利器
SpringMVC 系列第 4 篇:controller 常見的接收引數的方式
SpringMVC 系列第 5 篇:@RequestBody 大解密,說點你不知道的
SpringMVC 系列第 6 篇:上傳檔案的 4 種方式,你都會么?
SpringMVC 系列第 7 篇:SpringMVC 回傳視圖常見的 5 種方式,你會幾種?
SpringMVC 系列第 8 篇:回傳 json & 通用回傳值設計
SpringMVC 系列第 9 篇:SpringMVC 回傳 null 是什么意思?
SpringMVC 系列第 10 篇:異步處理
SpringMVC 系列第 11 篇:集成靜態資源
SpringMVC 系列第 12 篇:攔截器
SpringMVC 系列第 13 篇:統一例外處理
SpringMVC 系列第 14 篇:實戰篇:通用回傳值 & 例外處理設計
SpringMVC 系列第 15 篇:全注解的方式 & 原理決議
SpringMVC 系列第 16 篇:萬字長文,深度決議 SpringMVC 原始碼,讓你醍醐灌頂
13、更多好文章
Spring 高手系列(共 56 篇)
Java 高并發系列(共 34 篇)
MySql 高手系列(共 27 篇)
Maven 高手系列(共 10 篇)
Mybatis 系列(共 12 篇)
聊聊 db 和快取一致性常見的實作方式
介面冪等性這么重要,它是什么?怎么實作?
泛型,有點難度,會讓很多人懵逼,那是因為你沒有看這篇文章!
14、【路人甲 Java】所有系列高清 PDF
領取方式,掃碼發送:yyds

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/303037.html
標籤:java
下一篇:Java 經典小題篇
