谈谈ApplicationContextInitializer应用上下文初始化器
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自己没有提供任何的实现类。但小伙伴们可能都知道SpringBoot对它有较多的扩展实现。
本文因为纯在Spring Framework环境下,所以主要讲解它在org.springframework.web.context.ContextLoader以及org.springframework.web.servlet.FrameworkServlet中的应用。最后我会示例怎么样通过自定义的方式实现容器刷新前的自定义行为。
在ContextLoader中的应用
public class ContextLoader {
...
// 它是个list,也就是说可以向此容器注册N上下文初始化器
private final List<ApplicationContextInitializer<ConfigurableApplicationContext>> contextInitializers = new ArrayList<>();
// 为啥不用addAll? 因为此处泛型需要强转~
public void setContextInitializers(@Nullable ApplicationContextInitializer<?>... initializers) {
if (initializers != null) {
for (ApplicationContextInitializer<?> initializer : initializers) {
this.contextInitializers.add((ApplicationContextInitializer<ConfigurableApplicationContext>) initializer);
}
}
}
// ======================它的执行时机如下;======================
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
...
configureAndRefreshWebApplicationContext(cwac, servletContext);
...
}
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
...
customizeContext(sc, wac);
wac.refresh();
...
}
// 由此可见所有的ApplicationContextInitializer的执行,都发生在wac.refresh();之前
protected void customizeContext(ServletContext sc, ConfigurableWebApplicationContext wac) {
//这里是从ServletContext 读取配置
//因为我们可以这么配置的globalInitializerClasses:ApplicationContextInitializer实现类的全类名(可以逗号分隔配置多个)
// 或者key是它:contextInitializerClasses
// 拿到配置的这些全类名后,下面都要反射创建对象的~~~
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()));
}
// 创建好实例对象后,添加进全局的list里面(默认使用空构造函数)
this.contextInitializers.add(BeanUtils.instantiateClass(initializerClass));
}
// 采用order排序后,再分别执行~~~~
AnnotationAwareOrderComparator.sort(this.contextInitializers);
for (ApplicationContextInitializer<ConfigurableApplicationContext> initializer : this.contextInitializers) {
initializer.initialize(wac);
}
}
...
}
可以看到ContextLoader中对ApplicationContextInitializer的使用还是非常简单。它所做无非就是把ServletContext上下配置 + 本类自己配置的全部拿出来,在容器刷新之前执行而已。
在FrameworkServlet中的应用
这个是Spring MVC的核心API,被称为前端控制器。它会负责启动web子容器~
同样在这期间,它也会处理ApplicationContextInitializer:
public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
private final List<ApplicationContextInitializer<ConfigurableApplicationContext>> contextInitializers = new ArrayList<>();
// 添加的代码和上面一模一样~
public void setContextInitializers(@Nullable ApplicationContextInitializer<?>... initializers) {
if (initializers != null) {
for (ApplicationContextInitializer<?> initializer : initializers) {
this.contextInitializers.add((ApplicationContextInitializer<ConfigurableApplicationContext>) initializer);
}
}
}
// 执行时机
@Override
protected final void initServletBean() throws ServletException {
//...
this.webApplicationContext = initWebApplicationContext();
initFrameworkServlet();
//...
}
protected WebApplicationContext initWebApplicationContext() {
//...
configureAndRefreshWebApplicationContext(cwac);
//...
}
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
//...
postProcessWebApplicationContext(wac);
applyInitializers(wac);
wac.refresh();
}
protected void applyInitializers(ConfigurableApplicationContext wac) {
// 此处可以看到,只会拿globalInitializerClasses这个key配置的了~~~~ 只有全局的此处才会继续有用
String globalClassNames = getServletContext().getInitParameter(ContextLoader.GLOBAL_INITIALIZER_CLASSES_PARAM);
if (globalClassNames != null) {
for (String className : StringUtils.tokenizeToStringArray(globalClassNames, INIT_PARAM_DELIMITERS)) {
this.contextInitializers.add(loadInitializer(className, wac));
}
}
// 本Servlet里不仅仅可以直接set进来,也可以contextInitializerClasses直接配置全类名
if (this.contextInitializerClasses != null) {
for (String className : StringUtils.tokenizeToStringArray(this.contextInitializerClasses, INIT_PARAM_DELIMITERS)) {
this.contextInitializers.add(loadInitializer(className, wac));
}
}
// 最终排序、执行~~~
AnnotationAwareOrderComparator.sort(this.contextInitializers);
for (ApplicationContextInitializer<ConfigurableApplicationContext> initializer : this.contextInitializers) {
initializer.initialize(wac);
}
}
}
整体的执行流程,几乎和ContextLoader的一模一样,没什么太值得说的。
如何自定义一个ApplicationContextInitializer
我们已经知道Spring内部并没有提供任何一个ApplicationContextInitializer的实现,很显然这像是Spirng提供的一个SPI钩子接口,具体实现我们自己去定制接口。
上面说的都是ApplicationContextInitializer的它应用,它的执行。此处我们更应该关心的是:它是何时、怎么被注册进去的呢?
从上面源码分析也可以看出,在Spring环境下,我们自定义实现一个ApplicationContextInitializer让并且它生效的方式有两种:
- 手动调用他们的setXXX方法添加进去
- 通过ServletContext初始化参数放进去(其实如果是web.xml时代是配置即可)
此接口在Spring3.1开始提供的,所以很容易联想到它可以不依赖于web.xml配置方式,使用全注解驱动的方式也是可行的。
借助WebApplicationInitializer方式自定义实现
我们的需求:需要在容器启动之前注册我们自己的ApplicationContextInitializer。
我们知道Servlet3.0规范中提供了一个SPI来启动Spring容器,Spring对它进行了实现:
// @since 3.1
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
//...
}
Servlet容器启动时,它会加载进来所有的WebApplicationInitializer接口实现类。
分别看看AbstractContextLoaderInitializer和AbstractDispatcherServletInitializer,它们提供了可扩展的方法:
public abstract class AbstractContextLoaderInitializer implements WebApplicationInitializer {
// 它最终会被本类的此处调用:listener.setContextInitializers(getRootApplicationContextInitializers());
// 我们知道ContextLoaderListener所有事情都是委托给了ContextLoader类去完成的~
@Nullable
protected ApplicationContextInitializer<?>[] getRootApplicationContextInitializers() {
return null;
}
}
public abstract class AbstractDispatcherServletInitializer extends AbstractContextLoaderInitializer {
// 它最终会本类的此处调用dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());
@Nullable
protected ApplicationContextInitializer<?>[] getServletApplicationContextInitializers() {
return null;
}
}
由此可见,我们只需要在自己写的WebApplicationInitializer实现里,复写上述两个方法,即能达到自定义应用上下文初始化器的目的。
DEMO示例
先写一个初始化器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, JdbcConfig.class, AsyncConfig.class, ScheduldConfig.class};
}
/**
* web容器的配置类(SpringMVC配置文件) 子容器;
*/
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[]{WebMvcConfig.class};
}
...
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
2021-11-28 你知道为什么HashMap是线程不安全的吗?
2021-11-28 Java序列化与反序列化三连问:是什么?为什么要?如何做?
2021-11-28 什么情况用ArrayList or LinkedList呢?
2021-11-28 你能谈谈HashMap怎样解决hash冲突吗
2021-11-28 谈谈这几个常见的多线程面试题
2021-11-28 你能说说进程与线程的区别吗
2021-11-28 谈谈 Redis 的过期策略