ApplicationContextInitializer接口类的使用和原理解读

在看Ngbatis源码的过程中,看到了自定义的ApplicationContextInitializer实现类,对ApplicationContextInitializer接口不是特别的理解,所以趁此机会总结下对其的理解和使用。

1. 作用

  • ApplicationContextInitializer(系统初始化器),在 Spring 容器化开始的时候,ApplicationContextInitializer接口的所有实现在类被实例化。
  • 在Spring容器刷新前,所有实现类的 org.springframework.context.ApplicationContextInitializer#initialize 方法会被调用,initialize 方法的形参类型是 ConfigurableApplicationContext,因此可以认为 ApplicationContextInitializer 实际上是Spring容器初始化前 ConfigurableApplicationContext 的回调接口,可以对上下文环境作一些操作,如运行环境属性注册、激活配置文件等。
public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {

   /**
* Initialize the given application context.
* @param applicationContext the application to configure
*/
void initialize(C applicationContext); }

2. 加载方式

通过具体的实现类实现ApplicationContextInitializer接口。

Springboot的扩展点ApplicationContextInitializer接口的实现主要分为两步:

  1. 实现ApplicationContextInitializer接口
  2. 把实现类注册到Spring容器中

其中把实现了ApplicationContextInitializer接口的实现类注册到Spring容器中,主要有三种方式:

  • spring.factories
  • 启动类中配置
  • application.properties

以下一一介绍。

2.1. spring.factories

springboot会扫描所有jar包下的 META-INF/spring.factorties,比如预置以下内容,即可完成自定义MyApplicationContextInitializer的注册:

org.springframework.context.ApplicationContextInitializer=com.knqiufan.config.MyApplicationContextInitializer

2.2. 启动类中配置

在SpringBoot的启动类中,使用SpringApplication.addInitializers(new MyApplicationContextInitializer())完成自定义MyApplicationContextInitializer的注册:

@SpringBootApplication
public class TestApplication {
public static void main(String[] args) {
SpringApplication springApplication = new SpringApplication(TestApplication.class);
springApplication.addInitializers(new MyApplicationContextInitializer());
springApplication.run(args);
}
}

2.3. application.properties

在application.properties文件中预置以下配置内容,即可完成自定义MyApplicationContextInitializer的注册:

context.initializer.classes=com.knqiufan.config.MyApplicationContextInitializer

3. 初始化时机

ApplicationContextInitializer接口的实现类的初始化,是在SpringApplication类的构造函数中。

即Spring容器初始化开始前,先通过 org.springframework.boot.SpringApplication#getSpringFactoriesInstances 得到所有实现类的集合,然后通过 org.springframework.boot.SpringApplication#setInitializers 注入到SpringApplication类的initializers属性中:

/**
* SpringApplication构造函数
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// 获得所有实现类的集合
this.bootstrapRegistryInitializers = new ArrayList<>(
getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
// 给属性 List<ApplicationContextInitializer<?>> initializers 赋值
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}

org.springframework.boot.SpringApplication#getSpringFactoriesInstances 方法源码如下:

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = getClassLoader();
// Use names and ensure unique to protect against duplicates
// 使用Set保证名称唯一,以防止重复
Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}

可以看到调用的是 SpringFactoriesLoader.loadFactoryNames 静态方法来加载所有 ApplicationContextInitializer 的实现类名称。

SpringFactoriesLoader.loadFactoryNames 中调用了 loadSpringFactories 方法,在 loadSpringFactories 方法中就可以看到加载了 META-INF/spring.factories 文件,并将文件中的实现类加入到缓存中。源码如下:

public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
ClassLoader classLoaderToUse = classLoader;
if (classLoader == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
} String factoryTypeName = factoryType.getName();
return (List)loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
} private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
Map<String, List<String>> result = (Map)cache.get(classLoader);
if (result != null) {
return result;
} else {
Map<String, List<String>> result = new HashMap(); try {
// 获取 META-INF/spring.factories 下配置的所有实现类
Enumeration<URL> urls = classLoader.getResources("META-INF/spring.factories"); // ... 省略部分代码 ... // 加载的ApplicationContextInitializer实现类加入缓存
cache.put(classLoader, result);
return result;
} catch (IOException var14) {
throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var14);
}
}
}

4. 执行时机

ApplicationContextInitializer接口的实现类的执行时机是在org.springframework.boot.SpringApplication#prepareContext-->org.springframework.boot.SpringApplication#applyInitializers中,也就是Spring容器正式刷新前,准备上下文环境时。

/**
* Spring容器准备上下文环境方法
*/
private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
context.setEnvironment(environment);
postProcessApplicationContext(context);
// 执行 ApplicationContextInitializer 实现类的 initialize() 方法
applyInitializers(context);
//... 省略其他代码 ...
} /**
* 在此方法中执行 ApplicationContextInitializer 实现类中的 initialize() 方法
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
protected void applyInitializers(ConfigurableApplicationContext context) {
// 获取所有实现类,并进行遍历
for (ApplicationContextInitializer initializer : getInitializers()) {
Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(),
ApplicationContextInitializer.class);
Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
// 执行实现类的initialize方法
initializer.initialize(context);
}
}

浅浅看下 org.springframework.boot.SpringApplication#getInitializers 方法。

在前面初始化时,调用 org.springframework.boot.SpringApplication#setInitializers 方法将获取到的所有的 ApplicationContextInitializer 接口实现类加入到 initializers 属性中,在这里的 org.springframework.boot.SpringApplication#getInitializers 方法就负责将 initializers 属性中数据拿出来。

源码如下:

/**
* 返回使用 Order 排序过的 ApplicationContextInitializer 实现类的Set列表
* Returns read-only ordered Set of the {@link ApplicationContextInitializer}s that
* will be applied to the Spring {@link ApplicationContext}.
* @return the initializers
*/
public Set<ApplicationContextInitializer<?>> getInitializers() {
return asUnmodifiableOrderedSet(this.initializers);
} private static <E> Set<E> asUnmodifiableOrderedSet(Collection<E> elements) {
List<E> list = new ArrayList<>(elements);
list.sort(AnnotationAwareOrderComparator.INSTANCE);
return new LinkedHashSet<>(list);
}

5. 内置实现类

Springboot内部也有一些内置的实现类,用于辅助Spring相关功能的实现。简单介绍几个:

  • DelegatingApplicationContextInitializer
  • ContextIdApplicationContextInitializer
  • ConfigurationWarningsApplicationContextInitializer
  • ServerPortInfoApplicationContextInitializer

5.1. DelegatingApplicationContextInitializer

ApplicationContextInitializer 其中一个的实现方式就是在 application.properties 配置文件中对实现类进行配置。

DelegatingApplicationContextInitializer的作用就是找到application.properties文件中配置的实现类实例化,并执行initialize()方法。

源码很好理解,如下:

/**
* {@link ApplicationContextInitializer} that delegates to other initializers that are
* specified under a {@literal context.initializer.classes} environment property.
* 获取 context.initializer.classes 这个环境变量中配置的实现类,并执行
*
* @author Dave Syer
* @author Phillip Webb
* @since 1.0.0
*/
public class DelegatingApplicationContextInitializer
implements ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered { // NOTE: Similar to org.springframework.web.context.ContextLoader // 定义的环境变量名称
private static final String PROPERTY_NAME = "context.initializer.classes"; private int order = 0; @Override
public void initialize(ConfigurableApplicationContext context) {
// 根据配置上下文获取配置信息
ConfigurableEnvironment environment = context.getEnvironment();
// 获取所有配置的实现类
List<Class<?>> initializerClasses = getInitializerClasses(environment);
if (!initializerClasses.isEmpty()) {
// 执行实现类的 initialize 方法
applyInitializerClasses(context, initializerClasses);
}
} private List<Class<?>> getInitializerClasses(ConfigurableEnvironment env) {
// 获取 PROPERTY_NAME 环境变量名配置的所有实现类类名
String classNames = env.getProperty(PROPERTY_NAME);
List<Class<?>> classes = new ArrayList<>();
if (StringUtils.hasLength(classNames)) {
for (String className : StringUtils.tokenizeToStringArray(classNames, ",")) {
// 获取实现类
classes.add(getInitializerClass(className));
}
}
return classes;
} /**
* 获取实现类
*/
private Class<?> getInitializerClass(String className) throws LinkageError {
try {
Class<?> initializerClass = ClassUtils.forName(className, ClassUtils.getDefaultClassLoader());
Assert.isAssignable(ApplicationContextInitializer.class, initializerClass);
return initializerClass;
}
catch (ClassNotFoundException ex) {
throw new ApplicationContextException("Failed to load context initializer class [" + className + "]", ex);
}
} /**
* 执行实现类的 initialize 方法
*/
private void applyInitializerClasses(ConfigurableApplicationContext context, List<Class<?>> initializerClasses) {
Class<?> contextClass = context.getClass();
List<ApplicationContextInitializer<?>> initializers = new ArrayList<>();
for (Class<?> initializerClass : initializerClasses) {
initializers.add(instantiateInitializer(contextClass, initializerClass));
}
// 执行实现类的 initialize 方法
applyInitializers(context, initializers);
} private ApplicationContextInitializer<?> instantiateInitializer(Class<?> contextClass, Class<?> initializerClass) {
Class<?> requireContextClass = GenericTypeResolver.resolveTypeArgument(initializerClass,
ApplicationContextInitializer.class);
Assert.isAssignable(requireContextClass, contextClass,
() -> String.format(
"Could not add context initializer [%s] as its generic parameter [%s] is not assignable "
+ "from the type of application context used by this context loader [%s]: ",
initializerClass.getName(), requireContextClass.getName(), contextClass.getName()));
return (ApplicationContextInitializer<?>) BeanUtils.instantiateClass(initializerClass);
} @SuppressWarnings({ "unchecked", "rawtypes" })
private void applyInitializers(ConfigurableApplicationContext context,
List<ApplicationContextInitializer<?>> initializers) {
initializers.sort(new AnnotationAwareOrderComparator());
// 遍历所有 ApplicationContextInitializer 接口的实现类,执行实现类的 initialize 方法
for (ApplicationContextInitializer initializer : initializers) {
initializer.initialize(context);
}
} public void setOrder(int order) {
this.order = order;
} @Override
public int getOrder() {
return this.order;
} }

5.2. ContextIdApplicationContextInitializer

ContextIdApplicationContextInitializer用于设置 Spring 应用上下文 ID,如果在application.properties中未设置spring.application.name,则默认为 “application”。

以下只展示重点源码,有兴趣可以直接去查看完整代码:

public class ContextIdApplicationContextInitializer
implements ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered { // ... 省略部分代码 ... @Override
public void initialize(ConfigurableApplicationContext applicationContext) {
ContextId contextId = getContextId(applicationContext);
applicationContext.setId(contextId.getId());
applicationContext.getBeanFactory().registerSingleton(ContextId.class.getName(), contextId);
} private ContextId getContextId(ConfigurableApplicationContext applicationContext) {
// ... 省略部分代码 ...
return new ContextId(getApplicationId(applicationContext.getEnvironment()));
}
// 设置 Spring 应用上下文 ID,若没设置则默认为 “application”
private String getApplicationId(ConfigurableEnvironment environment) {
String name = environment.getProperty("spring.application.name");
return StringUtils.hasText(name) ? name : "application";
} // ... 省略部分代码 ...
}

5.3. ConfigurationWarningsApplicationContextInitializer

ConfigurationWarningsApplicationContextInitializer用于报告 Spring 容器的一些常见的错误配置。

该初始化器为 context 增加了一个 Bean 的后置处理器。这个处理器是在注册 BeanDefinition 实例之后生效的,用于处理注册实例过程中产生的告警信息,其实就是通过日志打印出告警信息。

以下只展示重点源码,有兴趣可以直接去查看完整代码:

public class ConfigurationWarningsApplicationContextInitializer
implements ApplicationContextInitializer<ConfigurableApplicationContext> { private static final Log logger = LogFactory.getLog(ConfigurationWarningsApplicationContextInitializer.class); /**
* 增加了一个 Bean 的后置处理器,在注册 BeanDefinition 实例之后生效。
* 用于处理注册实例过程中产生的告警信息,其实就是通过日志打印出告警信息。
*/
@Override
public void initialize(ConfigurableApplicationContext context) {
context.addBeanFactoryPostProcessor(new ConfigurationWarningsPostProcessor(getChecks()));
} /**
* Returns the checks that should be applied.
* @return the checks to apply
*/
protected Check[] getChecks() {
return new Check[] { new ComponentScanPackageCheck() };
} /**
* {@link BeanDefinitionRegistryPostProcessor} to report warnings.
*/
protected static final class ConfigurationWarningsPostProcessor
implements PriorityOrdered, BeanDefinitionRegistryPostProcessor { // ... 省略部分代码 ... @Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
for (Check check : this.checks) {
String message = check.getWarning(registry);
if (StringUtils.hasLength(message)) {
warn(message);
}
} }
// 输出警告信息日志
private void warn(String message) {
if (logger.isWarnEnabled()) {
logger.warn(String.format("%n%n** WARNING ** : %s%n%n", message));
}
} } // ... 省略部分代码 ...
}

5.4. ServerPortInfoApplicationContextInitializer

ServerPortInfoApplicationContextInitializer除了实现了ApplicationContextInitializer接口外,还实现了ApplicationListener接口,ServerPortInfoApplicationContextInitializer作用就是把自己作为一个监听器注册到Spring的上下文环境中。

public class ServerPortInfoApplicationContextInitializer implements
ApplicationContextInitializer<ConfigurableApplicationContext>, ApplicationListener<WebServerInitializedEvent> { @Override
public void initialize(ConfigurableApplicationContext applicationContext) {
// 注册为监听器
applicationContext.addApplicationListener(this);
} @Override
public void onApplicationEvent(WebServerInitializedEvent event) {
String propertyName = "local." + getName(event.getApplicationContext()) + ".port";
setPortProperty(event.getApplicationContext(), propertyName, event.getWebServer().getPort());
}
// ... 省略部分代码 ... }

6. 总结

  • 在Spring容器刷新前,所有实现类的org.springframework.context.ApplicationContextInitializer#initialize方法都会被调用,所以可以通过实现ApplicationContextInitializer接口对Spring上下文环境作一些配置或操作。
  • ApplicationContextInitializer接口的实现方式有三种,可以根据项目需要选择合适的
  • 深入理解了ApplicationContextInitializer接口实现类的初始化时机和执行时机
  • 了解了一些内部实现类的作用和实现方法,可以学习到Spring本身是如何利用扩展接口实现一些功能,在实际的项目开发中具有一定的参考意义。

[Ngbatis源码学习][SpringBoot] ApplicationContextInitializer接口类的使用和原理解读的更多相关文章

  1. Java并发包源码学习系列:阻塞队列BlockingQueue及实现原理分析

    目录 本篇要点 什么是阻塞队列 阻塞队列提供的方法 阻塞队列的七种实现 TransferQueue和BlockingQueue的区别 1.ArrayBlockingQueue 2.LinkedBloc ...

  2. THINKPHP源码学习--------文件上传类

    TP图片上传类的理解 在做自己项目上传图片的时候一直都有用到TP的上传图片类,所以要进入源码探索一下. 文件目录:./THinkPHP/Library/Think/Upload.class.php n ...

  3. JDK源码学习之 集合实现类

    一.HashMap (1) 简介:java1.8版本之前HashMap的结构图如下: 数组的每个元素都是一个单链表的头节点,链表是用来解决冲突的,如果不同的key映射到了数组的同一位置处,就将其放入单 ...

  4. 【STL源码学习】std::list类的类型别名分析

    有了点模板元编程的traits基础,看STL源码清晰多了,以前看源码的时候总被各种各样的typedef给折腾得看不下去, 将<list>头文件的类继承结构简化如下 #include < ...

  5. 老刘 Yii2 源码学习笔记之 Action 类

    Action 的概述 InlineAction 就是内联动作,所谓的内联动作就是放到controller 里面的 actionXXX 这种 Action.customAction 就是独立动作,就是直 ...

  6. JVM源码分析之深入分析Object类finalize()方法的实现原理

      原创申明:本文由公众号[猿灯塔]原创,转载请说明出处标注 ​“365篇原创计划”第十篇. 今天呢!灯塔君跟大家讲: 深入分析Object类finalize()方法的实现原理 finalize 如果 ...

  7. 老刘 Yii2 源码学习笔记之 Component 类

    类图关系 属性与方法 class Component extends BaseObject { private $_events = []; private $_eventWildcards = [] ...

  8. 老刘 Yii2 源码学习笔记之 Module 类

    关系类图 从上图可以看出 Application 类继承了 Module,在框架中的是非常重要角色. 加载配置 public function setModules($modules) { forea ...

  9. Java并发包源码学习系列:阻塞队列实现之PriorityBlockingQueue源码解析

    目录 PriorityBlockingQueue概述 类图结构及重要字段 什么是二叉堆 堆的基本操作 向上调整void up(int u) 向下调整void down(int u) 构造器 扩容方法t ...

  10. Java并发包源码学习系列:阻塞队列实现之DelayQueue源码解析

    目录 DelayQueue概述 类图及重要字段 Delayed接口 Delayed元素案例 构造器 put take first = null 有什么用 总结 参考阅读 系列传送门: Java并发包源 ...

随机推荐

  1. docker 镜像管理之 overlay2 最佳实践

    1. Docker 镜像 Docker 镜像是个只读的容器模板,它组成了 Docker 容器的静态文件系统运行环境 rootfs,是启动 Docker 容器的基础. Docker 镜像是容器的静态视角 ...

  2. xshell配置隧道转移规则

    钢铁知识库,一个学习python爬虫.数据分析的知识库.人生苦短,快用python. xshell是什么 通俗点说就是一款强大ssh远程软件,可以方便运维人员对服务器进行管理操作,功能很多朋友们自行探 ...

  3. 百度网盘(百度云)SVIP超级会员共享账号每日更新(2023.12.2)

    一.百度网盘SVIP超级会员共享账号 可能很多人不懂这个共享账号是什么意思,小编在这里给大家做一下解答. 我们多知道百度网盘很大的用处就是类似U盘,不同的人把文件上传到百度网盘,别人可以直接下载,避免 ...

  4. 精通 VS 调试技巧,学习与工作效率翻倍!

    ​ 欢迎大家来到贝蒂大讲堂 ​ 养成好习惯,先赞后看哦~ ​ 所属专栏:C语言学习 ​ 贝蒂的主页:Betty's blog ​ 1. 什么是调试 当我们写代码时候常常会遇见输出结果不符合我们预期的情 ...

  5. [转帖]Linux下在文件内部指定行(首行、末尾行等)插入内容

    https://blog.csdn.net/drbing/article/details/52153766 1.在文件的首行插入指定内容: :~$ sed -i "1i#! /bin/sh ...

  6. 【转帖】nginx变量使用方法详解-1

    https://www.diewufeiyang.com/post/575.html Nginx 的配置文件使用的就是一门微型的编程语言,许多真实世界里的 Nginx 配置文件其实就是一个一个的小程序 ...

  7. [百度贴吧]部分CPU的SPEC2006int 结果

    这些测试成绩基本上是本人自己测试的结果.下表中有来自spec官网的两个成绩,因为测试年份较早,系统环境和编译器都较老,测试成绩本人实测的还差,所以仅作为参考.部分测试启用了自动并行和附加的优化库,是为 ...

  8. [转帖]Traefik中诡异的502和504问题

    https://zhuanlan.zhihu.com/p/156138704 我们都知道在 Kubernetes 集群中通常会使用 Ingress 方案来统一代理集群内部的流量,而常用的 Ingres ...

  9. CentOS8 解决 yum元数据下载失败的方法

    背景 最近同事反馈CentOS的机器无法再现安装 rpm包了. 提示信息主要是下载源数据失败. 今天上午抽时间进行了一下简单验证, 晚上进行一下总结. 避免遗忘. 认为与Redhat被IBM收购之后, ...

  10. # 再次推荐github 6.7k star开源IM项目OpenIM性能测试及消息可靠性测试报告

    本报告主要分为两部分,性能测试和消息可靠性测试.前者主要关注吞吐,延时,同时在线用户等,即通常所说的性能指标.后者主要模拟真实环境(比如离线,在线,弱网)消息通道的可靠性. 先说结论,对于容量和性能: ...