初始化实现方式
- Spring 运用于Web应用,其Ioc容器使用的类型为
org.springframework.web.context.WebApplicationContext
类 。 - 在Web应用中Spring的上下文容器可以称之为
RootApplicationContext
,而MVC的上下文容器称之为WebApplicationContext
或者ServletApplicationContext
。两者是父子容器的关系。具体Spring 容器见 Spring4.x 笔记(2):Spring 的Ioc容器 - Spring 框架提供了用于启动Web应用上下文容器的监听器:
ContextLoaderListener
,且容器的初始化需要ServletContext
对象的支持。
- 需要在
Web.xml
中配置web容器监听器ServletContextListener
- 或Servlet3.x中在web容器初始化接口中手动编码创建监听器
ServletContextListener
对象
使用 Web.xml 配置启动
- Schema 配置文件实现
- 定义监听器
ContextLoaderListener
对象 - 指定上下文参数
contextConfigLocation
自定义配置文件地址。多个配置文件可以使用逗号、空格等分隔。不显式指定资源类型前缀,默认从Web的根路径获取
// 手动指定配置文件路径
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:/spring/spring-context.xml</param-value>
</context-param>
# 定义监听器
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
- Web.xml 与Java Config 实现的配置类结合使用。使用注解
@Configuration
实现的Java类提供配置信息,web.xml
可以配置如下:
- 配置
contextClass
参数,指定Spring 使用自定义的AnnotationConfigWebApplicationContext
上下文容器类型替代默认的XmlWebApplicationContext
类型 - 配置初始化参数
contextConfigLocation
为Java Config 实现的配置类。一般为标注了@Configuration
的类。
# JavaConfig 配置,配置 contextClass 参数,指定Spring 使用 AnnotationConfigWebApplicationContext 上下文
<context-param>
<param-name>contextClass</param-name>
<param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
</context-param>
# 指定配置类:配置标注了@Configuration 的类
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>cn.com.sinosoft.smp.web.sys.config.SysConfig</param-value>
</context-param>
# 监听器,会根据上面配置使用 AnnotationConfigWebApplicationContext 根据 contextConfigLocation 指定的配置类启动容器
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
使用 SpringServletContainerInitializer 配置启动
- Servlet3.x 新增 ServletContext 的性能增强:( Servlet笔记系列(12):Servlet3.X版本新特性)
- 使用web容器初始化接口
ServletContainerInitializer
取代web.xml
配置 - 必须在
/META-INF/services/javax.servlet.ServletContainerInitializer
文件中配置实现类的全路径类名 - 实现类需要使用
@HandlesTypes
注解来指定希望被处理的类,过滤掉不希望给onStartup()
处理的类
- Spring 中
SpringServletContainerInitializer
实现了ServletContainerInitializer
接口:
- 在 spring-web 模块中有如下配置:
/META-INF/services/javax.servlet.ServletContainerInitializer
文件,内容为org.springframework.web.SpringServletContainerInitializer
- 并且通过
@HandlesTypes
注解指定WebApplicationInitializer
来处理具体的业务
- 在实现自定义配置时就可以实现
WebApplicationInitializer
接口,重写onStartup
方法,依次添加各种配置:
public class WebInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
# 注册上下文与配置,这边使用 Xml 实现或者 JavaConfig 实现都可以
AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
ctx.register(MvcConfig.class);
ctx.setServletContext(servletContext);
// 手动创建 ContextLoaderListener 对象
servletContext.addListener(new ContextLoaderListener(ctx));
// 添加 CharacterEncodingFilter 拦截器
CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
characterEncodingFilter.setEncoding("utf-8");
FilterRegistration.Dynamic registration = servletContext.addFilter("characterEncodingFilter", characterEncodingFilter);
registration.setAsyncSupported(isAsyncSupported());
registration.addMappingForUrlPatterns(getDispatcherTypes(), false, "/*");
// 配置 spring mvc, 这边可以配置servlet的配置
ServletRegistration.Dynamic servlet = servletContext.addServlet("dispatcher", new DispatcherServlet(ctx));
servlet.addMapping("/");
servlet.setLoadOnStartup(1);
}
private boolean isAsyncSupported() {
return true;
}
private EnumSet<DispatcherType> getDispatcherTypes() {
return isAsyncSupported() ?
EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.INCLUDE, DispatcherType.ASYNC) :
EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.INCLUDE);
}
}
- Spring 框架实现了一套
WebApplicationInitializer
接口的默认实现。
- 核心模块有实现类
AbstractContextLoaderInitializer
- Mvc 模块有实现类
AbstractDispatcherServletInitializer
、AbstractAnnotationConfigDispatcherServletInitializer
- 直接继承
AbstractAnnotationConfigDispatcherServletInitializer
类,覆盖默认的方法指定配置类,即可方便实现初始化的功能 getRootConfigClasses
指定 Spring 配置类getServletConfigClasses
指定Spring Mvc 配置类getServletMappings
指定Spring Mvc的DispatcherServlet
的映射
public class WebInitializer2 extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{ApplicationConfig.class};
}
@Override
protected Class<?>[] getServletConfigClasses() {
return null;
}
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
# Spring 配置类
@Configuration
@ComponentScan(basePackages = {"com.learning.spring.base.javaconfig"},
excludeFilters = {@ComponentScan.Filter(classes = Controller.class)})
public class ApplicationConfig {
}
AbstractAnnotationConfigDispatcherServletInitializer
实现类的类图如下
容器初始化
监听器对象的创建
WebApplicationContext
上下文容器的初始化需要ContextLoaderListener
监听器的支持,该监听器有两个构造函数,两种构造函数,正好对应着Web.xml
配置和SpringServletContainerInitializer
初始化类配置两种不同的实现方式。
构造函数类型 | 对应初始化方式 | 分析 |
---|---|---|
无参构造 | Web.xml | 创建监听器对象,然后通过实现的ServletContextListener 接口方法contextInitialized() 创建和初始化容器WebApplicationContext |
有参构造 | SpringServletContainerInitializer | 1、参数为WebApplicationContext 对象,即先创建了容器对象WebApplicationContext 2、创建监听器对象,通过实现的 ServletContextListener 接口方法contextInitialized 方法初始化容器WebApplicationContext |
SpringServletContainerInitializer
方式创建上下文容器对象在AbstractContextLoaderInitializer#registerContextLoaderListener
方法中实现,该方法创建了WebApplicationContext
对象,并且使用ContextLoaderListener
类的有参构造函数,把创建的容器对象作为参数创建监听器实例
- 值得注意的是在创建容器对象的方法中,首先会获取容器相关的配置类,然后再创建
AnnotationConfigWebApplicationContext
类型的容器对象 - 获取容器相关的配置类的方法
getRootConfigClasses
,框架中没有实现,需要应用扩展使用。
@Override
protected WebApplicationContext createRootApplicationContext() {
# 获取配置类方法 getRootConfigClasses,需要应用自己扩展实现
Class<?>[] configClasses = getRootConfigClasses();
if (!ObjectUtils.isEmpty(configClasses)) {
AnnotationConfigWebApplicationContext rootAppContext = new AnnotationConfigWebApplicationContext();
rootAppContext.register(configClasses);
return rootAppContext;
}
else {
return null;
}
}
监听器初始化
ContextLoaderListener
继承自ServletContextListener
这个ServletContext
监听接口,覆盖了初始化方法contextInitialized()
,该方法在应用启动时将被调用。在该方法主要的工作是完成容器WebApplicationContext
的初始化
@Override
public void contextInitialized(ServletContextEvent event) {
# 调用初始化方法
initWebApplicationContext(event.getServletContext());
}
- 在
WebApplicationContext
创建初始化过程,简单总结为如下4个步骤:
- 创建。判断上下文容器对象是否已经创建,如果没有则创建一个新的容器对象
- 初始化。判断容器是否成功初始化(
isActive
状态),如未初始化,则执行容器的初始化工作 - 设置容器关系。如果该容器有父容器,则还会设置其父容器。目前的设计来看,Spring 的容器为父容器,而Spring MVC 的容器为子容器。
- 保存。初始化完成后,存入
ServletContext
对象中,以便后续在Web应用程序中使用。其中key
为org.springframework.web.context.WebApplicationContext.ROOT
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
... ...
try {
# 1- 创建上下文容器。先判断上下文容器对象是否已经创建
# Web.xml 配置实现,在这边创建上下文对象
# Servlet3.x方式实现中,已经提前了应用级别上下文对象,这边不需要创建
if (this.context == null) {
this.context = createWebApplicationContext(servletContext);
}
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
# 2- 初始化
if (!cwac.isActive()) {
# 3-设置关系。 应用级别的容器本身就是父容器,不需要设置父容器
if (cwac.getParent() == null) {
ApplicationContext parent = loadParentContext(servletContext);
cwac.setParent(parent);
}
# 初始化。上下文配置与初始化。重点。
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
# 4-保存。设置到 servletContext。
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
... ...
return this.context;
}
... ...
}
- 容器创建方法
createWebApplicationContext
,只有在使用Web.xml 配置方式时才会被调用。先获取获取创建上下文容器的类型,然后利用反射直接创建。
protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
# 获取创建上下文容器的类型
Class<?> contextClass = determineContextClass(sc);
... ...
# 反射创建
return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
}
- 在获取创建上下文容器的类型的方法中,需要注意的是框架默认创建的是
XmlWebApplicationContext
类型,可以通过初始化参数contextClass
自定义类型,如开篇手动指定的的AnnotationConfigWebApplicationContext
类型
protected Class<?> determineContextClass(ServletContext servletContext) {
# 获取初始化参数 contextClass的值,看是否有指定的Ioc容器类的类型
String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
if (contextClassName != null) {
try {
return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
}
... ...
}
else {
# 没有指定,就使用配置文件(ContextLoader.properties)中默认配置的,为 XmlWebApplicationContext 类型
contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
try {
return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
}
... ...
}
}
- 上下文容器配置与初始化方法
configureAndRefreshWebApplicationContext()
是整个过程的核心。所有初始化的工作部分都是在这边完成的,主要有:
编号 | 任务名称 | 具体事项 |
---|---|---|
1 | 设置容器的唯一Id | 1、可以使用初始化参数contextId 自定义2、默认值为一个组合值,为 WebApplicationContext 类的全路径名称加上ContextPath 的值。如当ContextPath 为空时,容器ID为org.springframework.web.context.WebApplicationContext: |
2 | 设置 ServletContext 属性 | 把ServletContext 对象作为属性存入应用上下文容器。 |
3 | 设置 contextConfigLocation 属性 | 获取初始化参数contextConfigLocation ,并且设置到容器对象的属性中 |
4 | 初始化占位符属性源 | 这个操作在容器初始化(刷新操作)始终会被执行。这边执行的原因在于确保servlet 属性源在刷新操作之前的任何后处理或初始化中都处于适当的位置 |
5 | 自定义容器设置 | 1、在容器初始化刷新之前自定义创建的容器 2、该自定义扩展功能专门由一个接口 ApplicationContextInitializer 实现3、通过初始化属性 contextInitializerClasses ,globalInitializerClasses ,指定自定义的ApplicationContextInitializer 接口实现类 器4、通过这个功能可以在容器对象初始化之前,扩展容器的功能 |
6 | 上下文初始化 | 调用AbstractApplicationContext#refresh 方法完成初始化。具体见 Spring4.x 笔记(6):Ioc 容器高级-内部工作机制 |
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
# 设置容器ID
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
if (idParam != null) {
wac.setId(idParam);
}
... ...
}
# 设置 `ServletContext` 属性
wac.setServletContext(sc);
# 设置 `contextConfigLocation` 属性
String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
if (configLocationParam != null) {
wac.setConfigLocation(configLocationParam);
}
# 初始化占位符属性源 。
# wac环境的 #initPropertySources 方法在上下文 refresh 的过程会总会被调用,这边提前处理一次。
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
}
# 自定义容器设置。
customizeContext(sc, wac);
# 上下文刷新,即初始化
wac.refresh();
}
监听器销毁
在监听器的销毁方法中执行上文容器对象的关闭工作(close)以及清空 ServletContext
中的上下文容器对象并且销毁ServletContext
中所有类路径以org.springframework.
开头的DisposableBean
接口的的实现类。