准备工作
- Git
- Gradle
- IDEA 2018以上版本
- JDK 1.8+
从https://github.com/spring-projects/spring-frameworkfork出一份源代码到自己的仓库,方便自己能写注释,以及运行test等。
当前使用的是Spring 5.1.3.BUILD-SNAPSHOT的版本
spring框架里的web模块包括webmvc、websocket、webflux,其中,web模块是另外三个模块的父模块。
代码拉下来以后,执行./gradlew :spring-oxm:compileTestJava
出现success即为成功。
容器
在Spring MVC初始化时,总共涉及到两个容器,一个是Root WebApplicationContext,还有一个是Servlet WebApplicationContext。
看一下常用的web.xml的配置:
1 | <!-- 省略非关键的配置 --> |
Root WebApplication
首先进入的就是ContextLoaderListener#contextInitialized
方法,这个监听实现了ServletContextListener
,是一个servlet与容器进行通信的接口,所以如果在web.xml中配置了<Listener>
标签以后,容器启动时就会启动该监听,并执行contextInitialized
方法,这个方法继承自contextLoader
,所以直接会调用ContextLoader#initWebApplicationContext
方法,顾名思义,这个方法就是用来初始化Root wac(Web Application Context简称)。
这里主要是调用了createWebApplicationContext
创建容器,和configureAndRefreshWebApplicationContext
配置和刷新容器。
创建wac:createWebApplicationContext
—>determineContextClass
,这个方法决定了要使用什么类型的wac,默认情况下,defaultStrategies
就是读取了ContextLoader.properties
的Properties,它指定了默认的wac类型是XmlWebApplicationContext
,源码如下:
1 | protected Class<?> determineContextClass(ServletContext servletContext) { |
返回一个类类型以后,在createWebApplicationContext
方法中,通过反射技术会创建该类类型的一个实例对象出来。接着会设置父容器,配置和刷新当前容器,但是在刚创建容器的时候,由于并没有显示指定容器中active这个AtomicBoolean的值,所以默认为false,这时候会进入设置父容器和配置wac的步骤。
如果wac的父容器为空,会设置wac的父容器。当前这个已经是Root wac了,所以其实loadParentContext
这个方法返回为空,并且没有任何一个子类去重写,由此得知,Root wac的父容器通常始终为空。
接下来会将wac放到ServletContext
(以下简称sc)的attribute里,由对应的Web容器放到自己的容器上下文里,在Tomcat里就是ApplicationContext
,该类里保存了一个ConcurrentHashMap
类型的成员变量attributes,保存所有属性,Spring的Root wac也放在这里。
配置wac:设置contextId
等属性—> 将sc设置到wac里—>获取web.xml
里的contextConfigLocation
配置路径—>初始化内容资源—>扫描用户配置的globalInitializerClasses
和contextInitializerClasses
—>初始化这些类—>刷新context
—>设置Root wac的active、closed的值,进入到Spring Ioc容器的初始化。
在这里,Spring为了适应不同的Web容器打破双亲委派机制的情况,有如下代码:
1 | ClassLoader ccl = Thread.currentThread().getContextClassLoader(); |
拿Tomcat举例来说,Tomcat打破了JVM的双亲委派机制:
CommonClassLoader
:加载那些能在Web应用和Tomcat之间共享的类;
SharedClassLoader
:Web应用之间能够共享的类,比如Spring;
CatalinaClassLoader
:加载Tomcat自身需要的类;
WebAppClassLoader
:每一个Web应用都有自己的WebAppClassLoader
,打破了双亲委派,它会首先从本地缓存查找是否加载过,然后再去使用父加载器去查找,如果没有接着会使用ExtClassLoader
(也可以说会使用BootstrapClassLoader
,避免Web应用的类覆盖JRE类),然后会在本地文件系统中查找,最后会交由系统类加载器(因为Class.forName默认使用的就是AppClassLoader)。
使用WebAppClassLoader加载的业务类可以通过Thread.currentThread().getContextClassLoader()
来获得类加载器;
使用ContextLoader.class.getClassLoader()
获得的是ContextLoader类加载器,如果两个类加载器是一致的,就可以将wac作为全局的静态变量currentContext
的值,如果不是一致的,就需要用一个线程安全的ConcurrentHashMap
来保存当前创建的wac,通过这种方式来保证线程间wac的私有。
Servlet WebApplication
第二个Servlet WebApplication容器,是在DispatcherServlet
初始化的过程中进行创建的。其实本质而言,这就是一个最正常的Servlet,和平时常见的的那种执行doGet,doPost的servlet没有任何区别,使用IDEA生成类图(command+alt+u
)如下:
HttpServletBean
:将ServletConfig
设置到Servlet对象中FrameworkServlet
:初始化ServletBean,创建Servlet wacDispatcherServlet
:初始化Spring MVC中的九个组件
HttpServletBean
这个类实现了EnvironmentCapable
, EnvironmentAware
接口,通过Spring的*Aware这类接口的感知能力,将environment注入进来。
在init这个方法中遍历所有的<init-param>
标签中配置的参数,封装成PropertyValues
,实现类是继承了MutablePropertyValues
的ServletConfigPropertyValues
,并将当前的Servlet对象转化成一个BeanWrapper
对象,同时将pvs放到这个bw里。
1 |
|
在ServletConfigPropertyValues
这个类的构造函数里,通过构造一个set集合来判断属性是否齐全:
1 | public ServletConfigPropertyValues(ServletConfig config, Set<String> requiredProperties) |
以上代码非常值得学习。
FrameWork Servlet
该类中有一个非常重要的参数:WebApplicationContext。对于这个servlet wac的赋值,可以有四种方式:
FrameWork Servlet
的构造函数- 实现了
ApplicationContextAware
接口,可以使用Spring的注入从而调用#setApplicationContext
#findWebApplicationContext
方法#createWebApplicationContext
方法
在父类HttpServletBean
的#init
方法里,最后一行就是调用本类中的#initServletBean
方法,这个方法的重点是#initWebApplicationContext
方法,而#initFrameworkServlet
方法由于没有任何实现,所以暂时不管。
1 | protected WebApplicationContext initWebApplicationContext() { |
过程比较复杂的是创建Servlet WebApplicationContext的过程,在#createWebApplicationContext
这个方法里,还是采用的和Root wac一样的创建方式,也就是默认XmlWebApplciationContext
的类型反射创建wac,然后设置父容器,设置contextConfigLocation参数,这个参数就是上一节中说过通过反射赋值到FrameworkServlet中的,随后在#configureAndRefreshWebApplicationContext
方法中,设置了ServletContext,ServletConfig,NameSpace,基本和ContextLoader
类里Root wac的创建方式类似。但是—这里随后又注册了一个ApplicationEventListener
,简单提一下Spring中的事件机制:
- 事件-
ApplicationEvent extends EventObject
- 发布方-
ApplicationEventPublisher
- 消费方-
ApplicationListener<E extends ApplicationEvent> extends EventListener
和容器相关的事件的又分为:
跟监听有关的类图如下:
#configureAndRefreshWebApplicationContext
注册了一个SourceFilteringListener
,这个listener专门用来监听和传入的wac有关的事件,当产生容器刷新事件时,实际上是执行refresh方法以后,会发送对应的事件类型,这里就是ContextRefreshedEvent
类型的事件,被SourceFilteringListener
监听到以后,在#onApplicationEvent
方法中判断事件源是否是传入的wac,如果是,交由代理ContextRefreshListener
的实例去执行对应的#onApplicationEvent
方法,而ContextRefreshListener
实际上是FrameworkServlet
的私有内部类,重写了#onApplicationEvent
方法,那么这个方法的作用其实很简单—调用外部类FrameworkServlet的onRefresh方法,初始化9大组件。
最后,将新创建的这个Servlet wac放到Servlet Context的attribute中。
总结:
在FrameworkServlet中,如果Servlet wac还没有激活,就会去配置,并且执行Spring Ioc容器的刷新,然后监听事件,触发类中的onRefresh
如果Servlet wac没有找到或者为空,则执行创建,同样执行Spring Ioc的刷新,触发onRefresh
- 如果Servlet wac已经激活,需要判断是否已经收到过Ioc容器的容器刷新事件,如果没有,同样要触发onRefresh
Dispatcher Servlet
这个类是核心调度类,但是其调用逻辑相对来说并不算复杂,只是代码量不小,其核心方法如下:
1 | protected void initStrategies(ApplicationContext context) { |
另外配一幅网上找的图:
其具体的工作流程,留待后面请求处理详解里在细细梳理。
总结
至此,Spring MVC容器初始化部分的内容到此结束,下一节开始学习Spring MVC对于Servlet 3.0的支持、SPI机制、以及Spring Aware的动态感知。
参考
芋艿的源码解析博客:http://www.iocoder.cn/