[Ngbatis源码学习][SpringBoot] ApplicationContextInitializer接口类的使用和原理解读
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接口的实现主要分为两步:
- 实现ApplicationContextInitializer接口
- 把实现类注册到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接口类的使用和原理解读的更多相关文章
- Java并发包源码学习系列:阻塞队列BlockingQueue及实现原理分析
目录 本篇要点 什么是阻塞队列 阻塞队列提供的方法 阻塞队列的七种实现 TransferQueue和BlockingQueue的区别 1.ArrayBlockingQueue 2.LinkedBloc ...
- THINKPHP源码学习--------文件上传类
TP图片上传类的理解 在做自己项目上传图片的时候一直都有用到TP的上传图片类,所以要进入源码探索一下. 文件目录:./THinkPHP/Library/Think/Upload.class.php n ...
- JDK源码学习之 集合实现类
一.HashMap (1) 简介:java1.8版本之前HashMap的结构图如下: 数组的每个元素都是一个单链表的头节点,链表是用来解决冲突的,如果不同的key映射到了数组的同一位置处,就将其放入单 ...
- 【STL源码学习】std::list类的类型别名分析
有了点模板元编程的traits基础,看STL源码清晰多了,以前看源码的时候总被各种各样的typedef给折腾得看不下去, 将<list>头文件的类继承结构简化如下 #include < ...
- 老刘 Yii2 源码学习笔记之 Action 类
Action 的概述 InlineAction 就是内联动作,所谓的内联动作就是放到controller 里面的 actionXXX 这种 Action.customAction 就是独立动作,就是直 ...
- JVM源码分析之深入分析Object类finalize()方法的实现原理
原创申明:本文由公众号[猿灯塔]原创,转载请说明出处标注 “365篇原创计划”第十篇. 今天呢!灯塔君跟大家讲: 深入分析Object类finalize()方法的实现原理 finalize 如果 ...
- 老刘 Yii2 源码学习笔记之 Component 类
类图关系 属性与方法 class Component extends BaseObject { private $_events = []; private $_eventWildcards = [] ...
- 老刘 Yii2 源码学习笔记之 Module 类
关系类图 从上图可以看出 Application 类继承了 Module,在框架中的是非常重要角色. 加载配置 public function setModules($modules) { forea ...
- Java并发包源码学习系列:阻塞队列实现之PriorityBlockingQueue源码解析
目录 PriorityBlockingQueue概述 类图结构及重要字段 什么是二叉堆 堆的基本操作 向上调整void up(int u) 向下调整void down(int u) 构造器 扩容方法t ...
- Java并发包源码学习系列:阻塞队列实现之DelayQueue源码解析
目录 DelayQueue概述 类图及重要字段 Delayed接口 Delayed元素案例 构造器 put take first = null 有什么用 总结 参考阅读 系列传送门: Java并发包源 ...
随机推荐
- fusionpbx简介
概述 fusionpbx是以freeswitch作为底层框架开发而成的开源PBX,在freeswitch的基础上,优化了GUI的易用性. fusionpbx可用作高可用性的单租户或基于域的多租户 PB ...
- C#设计模式06——适配器的写法
什么是适配器模式? 适配器模式是一种结构型设计模式,用于将现有接口转换为符合客户端期望的接口.适配器模式允许不兼容的类可以相互协作. 为什么需要适配器模式? 在实际开发中,经常会遇到需要复用一些已有的 ...
- 线性代数 · 矩阵 · Matlab | 满秩分解代码实现
背景 - 矩阵的满秩分解: 若 A 为 m×n 矩阵,rank(A) = r,则存在 F m×r.G r×n,使得 A = FG. 其中,F 列满秩,G 行满秩. 求满秩分解的方法: 得到 A 的行最 ...
- Spring cloud gateWay 限流器限流(一)
转载请注明出处: spring cloud 提供了限流操作的功能,其使用步骤如下: 1.引入maven依赖: <dependency> <groupId>org.springf ...
- Nacos源码 (3) 注册中心
本文将从一个服务注册示例入手,通过阅读客户端.服务端源码,分析服务注册.服务发现原理. 使用的2.0.2的版本. 客户端 创建NacosNamingService对象 NacosNamingServi ...
- 同步FIFO设计
FIFO有一个读口和一个写口,读写时钟一致是同步FIFO,时钟不一致就是异步FIFO IP设计中通常使用的是同步FIFO 异步FIFO通常使用在跨时钟域设计中 RAM(Random Access Me ...
- 解决windows系统电脑内存占用过高,一开机就是60%70%80%90%?
1.问题 windows系统电脑内存占用过高,一开机就是60%70%80%90%? 2.解决方式 主要是虚拟内存一直没有及时释放导致的 先贴上B站视频链接:解决windows系统电脑内存占用过高 这里 ...
- [转帖]Linux cache参数调优
https://zhuanlan.zhihu.com/p/136237953 缓存机制(cache)是保证Linux环境下对硬盘/flash操作效率的有效方式.cache建立在内存中,它缓存了硬盘/f ...
- [转帖]ARM64体系结构编程与实践:基础知识
ARM64体系结构编程与实践:基础知识 原创 异步社区 2022-03-30 12:44:16 著作权 文章标签 寄存器 体系结构 v8 ARM64体系结构 ARM 文章分类 物联网 阅读数1570 ...
- [转帖]linux 调优各项监控指标小记
https://z.itpub.net/article/detail/8A4E4E96522BD59D45AB5A4CA442EDB3 自开始负责生产环境部署,中间遇到了若干线上环境内存以及CPU的问 ...