ApplicationContextInitializer在Spring容器执行refresh之前执行
ApplicationContextInitializer用于在刷新Spring容器之前的回调接口。
ApplicationContextInitializer是Spring框架原有的概念, 这个类的主要目的就是在 ConfigurableApplicationContext类型(或者子类型)的ApplicationContext进行刷新refresh之前,允许我们对ConfigurableApplicationContext的实例做进一步的设置或者处理。
通常用于需要对应用程序进行某些初始化工作的web程序中。例如利用Environment上下文环境注册属性源、激活配置文件等等。另外它支持Ordered和@Order方式排序执行~
// @since 3.1
public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {
// Initialize the given application context.
void initialize(C applicationContext);
}
此接口,Spring Framework自己没有提供任何的实现类。
本文主要讲解它在org.springframework.web.context.ContextLoader以及org.springframework.web.servlet.FrameworkServlet中的应用。
在ContextLoader中的执行时机
源码-来自:ContextLoaderListener
源码-来自:ContextLoader
在FrameworkServlet中的执行时机
这个是Spring MVC的核心API,被称为前端控制器。它会负责启动web子容器~
同样在这期间,它也会处理ApplicationContextInitializer。
源码-来自:HttpServletBean
源码-来自:FrameworkServlet
先获取WebApplicationContext,如果已存在,则直接调用configureAndRefreshWebApplicationContext进行刷新,否则需要就创建。
如何自定义一个ApplicationContextInitializer
我们已经知道Spring内部并没有提供任何一个ApplicationContextInitializer的实现,具体实现我们自己去定制接口。
上面说的都是ApplicationContextInitializer它的执行。此处我们更应该关心的是:它是何时、怎么被注册进去的呢?
借助WebApplicationInitializer方式自定义实现
我们知道Servlet3.0规范中提供了一个SPI来启动Spring容器,Spring对它进行了实现:
// @since 3.1
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
//...
}
Servlet容器启动时,它会加载进来所有的WebApplicationInitializer接口实现类。
WebApplicationInitializer的继承关系:
源码-来自:AbstractDispatcherServletInitializer
1)序号①调用了父类AbstractContextLoaderInitializer的方法创建spring容器
2)需要②调用了自身的方法创建springmvc容器,并实例化了FrameworkServlet
所以我们使用全注解方法开发Spring应用的时候,在继承AbstractAnnotationConfigDispatcherServletInitializer的同时,复写getRootApplicationContextInitializers和getServletApplicationContextInitializers这两个方法即可。
先写一个初始化器ApplicationContextInitializer实现类:
@Order(10)
public class MyApplicationContextInitializer implements ApplicationContextInitializer {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
// 该方法此处不能用 因为还没初始化会报错:call 'refresh' before accessing beans via the ApplicationContext
//int beanDefinitionCount = applicationContext.getBeanDefinitionCount();
System.out.println(applicationContext.getApplicationName() + ":" + applicationContext.getDisplayName());
}
}
再复写WebApplicationInitializer实现类的相关方法:把我们自定义的初始化器return
/**
* 自己实现 基于注解驱动的ServletInitializer来初始化DispatcherServlet
*/
public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected ApplicationContextInitializer<?>[] getServletApplicationContextInitializers() {
return new ApplicationContextInitializer[]{new MyApplicationContextInitializer()};
}
@Override
protected ApplicationContextInitializer<?>[] getRootApplicationContextInitializers() {
return new ApplicationContextInitializer[]{new MyApplicationContextInitializer()};
}
/**
* 根容器的配置类;(Spring的配置文件) 父容器;
*/
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[]{RootConfig.class};
}
/**
* web容器的配置类(SpringMVC配置文件) 子容器;
*/
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[]{WebMvcConfig.class};
}
//...
}
通过ServletContext初始化参数放进去
在分析ContextLoader中的执行时机的时候,我们看到有这么一行代码
public static final String GLOBAL_INITIALIZER_CLASSES_PARAM = "globalInitializerClasses";
public static final String CONTEXT_INITIALIZER_CLASSES_PARAM = "contextInitializerClasses";
protected List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>>
determineContextInitializerClasses(ServletContext servletContext) {
List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> classes =
new ArrayList<>();
String globalClassNames = servletContext.getInitParameter(GLOBAL_INITIALIZER_CLASSES_PARAM);
if (globalClassNames != null) {
for (String className : StringUtils.tokenizeToStringArray(globalClassNames, INIT_PARAM_DELIMITERS)) {
classes.add(loadInitializerClass(className));
}
}
String localClassNames = servletContext.getInitParameter(CONTEXT_INITIALIZER_CLASSES_PARAM);
if (localClassNames != null) {
for (String className : StringUtils.tokenizeToStringArray(localClassNames, INIT_PARAM_DELIMITERS)) {
classes.add(loadInitializerClass(className));
}
}
return classes;
}
这里是通过ServletContext初始化参数放进去的,在web.xml时代,我们只需要在web.xml中配置即可。
第一种:使用globalInitializerClasses属性来配置,在web.xml文件中的配置如下:
<context-param>
<param-name>globalInitializerClasses</param-name>
<param-value>com.harvey.MyApplicationContextInitializer</param-value> <!--多个用逗号或者分号分隔-->
</context-param>
第二种:配置在DispatcherServlet的初始变量中:
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:spring-mvc.xml</param-value>
</init-param>
<!-- 配置初始化变量,将会调用set方法set进去 -->
<init-param>
<param-name>contextInitializerClasses</param-name>
<param-value>com.harvey.MyApplicationContextInitializer</param-value>
</init-param>
</servlet>