网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
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 是什么意思?
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
- SpringMVC 系列第 5 篇:@RequestBody 大解密,说点你不知道的
- SpringMVC 系列第 6 篇:上传文件的 4 种方式,你都会么?
- SpringMVC 系列第 7 篇:SpringMVC 返回视图常见的 5 种方式,你会几种?
- SpringMVC 系列第 8 篇:返回 json & 通用返回值设计
- SpringMVC 系列第 9 篇:SpringMVC 返回 null 是什么意思?
[外链图片转存中…(img-cxywCubz-1715530648797)]
[外链图片转存中…(img-0cjIorpl-1715530648798)]
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!