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. fusionpbx简介

    概述 fusionpbx是以freeswitch作为底层框架开发而成的开源PBX,在freeswitch的基础上,优化了GUI的易用性. fusionpbx可用作高可用性的单租户或基于域的多租户 PB ...

  2. C#设计模式06——适配器的写法

    什么是适配器模式? 适配器模式是一种结构型设计模式,用于将现有接口转换为符合客户端期望的接口.适配器模式允许不兼容的类可以相互协作. 为什么需要适配器模式? 在实际开发中,经常会遇到需要复用一些已有的 ...

  3. 线性代数 · 矩阵 · Matlab | 满秩分解代码实现

    背景 - 矩阵的满秩分解: 若 A 为 m×n 矩阵,rank(A) = r,则存在 F m×r.G r×n,使得 A = FG. 其中,F 列满秩,G 行满秩. 求满秩分解的方法: 得到 A 的行最 ...

  4. Spring cloud gateWay 限流器限流(一)

    转载请注明出处: spring cloud 提供了限流操作的功能,其使用步骤如下: 1.引入maven依赖: <dependency> <groupId>org.springf ...

  5. Nacos源码 (3) 注册中心

    本文将从一个服务注册示例入手,通过阅读客户端.服务端源码,分析服务注册.服务发现原理. 使用的2.0.2的版本. 客户端 创建NacosNamingService对象 NacosNamingServi ...

  6. 同步FIFO设计

    FIFO有一个读口和一个写口,读写时钟一致是同步FIFO,时钟不一致就是异步FIFO IP设计中通常使用的是同步FIFO 异步FIFO通常使用在跨时钟域设计中 RAM(Random Access Me ...

  7. 解决windows系统电脑内存占用过高,一开机就是60%70%80%90%?

    1.问题 windows系统电脑内存占用过高,一开机就是60%70%80%90%? 2.解决方式 主要是虚拟内存一直没有及时释放导致的 先贴上B站视频链接:解决windows系统电脑内存占用过高 这里 ...

  8. [转帖]Linux cache参数调优

    https://zhuanlan.zhihu.com/p/136237953 缓存机制(cache)是保证Linux环境下对硬盘/flash操作效率的有效方式.cache建立在内存中,它缓存了硬盘/f ...

  9. [转帖]ARM64体系结构编程与实践:基础知识

    ARM64体系结构编程与实践:基础知识 原创 异步社区 2022-03-30 12:44:16 著作权 文章标签 寄存器 体系结构 v8 ARM64体系结构 ARM 文章分类 物联网 阅读数1570 ...

  10. [转帖]linux 调优各项监控指标小记

    https://z.itpub.net/article/detail/8A4E4E96522BD59D45AB5A4CA442EDB3 自开始负责生产环境部署,中间遇到了若干线上环境内存以及CPU的问 ...