一、ApplicationContextInitializer 介绍

1.1 作用

**ApplicationContextInitializer ** 接口用于在 Spring 容器刷新之前执行的一个回调函数,通常用于向 SpringBoot 容器中注入属性。

1.2 内置实现类

DelegatingApplicationContextInitializer

使用环境属性 context.initializer.classes 指定的初始化器(initializers)进行初始化工作,如果没有指定则什么都不做。

通过它使得我们可以把自定义实现类配置在 application.properties 里成为了可能。

ContextIdApplicationContextInitializer

设置Spring应用上下文的ID,会参照环境属性。至于Id设置为什么值,将会参考环境属性:
* spring.application.name
* vcap.application.name
* spring.config.name
* spring.application.index
* vcap.application.instance_index 如果这些属性都没有,ID 使用 application。

ConfigurationWarningsApplicationContextInitializer

对于一般配置错误在日志中作出警告

ServerPortInfoApplicationContextInitializer

 将内置 servlet容器实际使用的监听端口写入到 Environment 环境属性中。这样属性 local.server.port 就可以直接通过 @Value 注入到测试中,或者通过环境属性 Environment 获取。

SharedMetadataReaderFactoryContextInitializer

创建一个 SpringBoot和ConfigurationClassPostProcessor 共用的 CachingMetadataReaderFactory对象。实现类为:ConcurrentReferenceCachingMetadataReaderFactory

ConditionEvaluationReportLoggingListener

将 ConditionEvaluationReport写入日志。

二、实现方式

首先新建三个自定义类,实现 ApplicationContextInitializer 接口

public class FirstInitializer implements ApplicationContextInitializer {

    @Override
public void initialize(ConfigurableApplicationContext applicationContext) {
ConfigurableEnvironment environment = applicationContext.getEnvironment(); Map<String, Object> map = new HashMap<>();
map.put("key1", "First"); MapPropertySource mapPropertySource = new MapPropertySource("firstInitializer", map);
environment.getPropertySources().addLast(mapPropertySource); System.out.println("run firstInitializer");
} } public class SecondInitializer implements ApplicationContextInitializer { @Override
public void initialize(ConfigurableApplicationContext applicationContext) {
ConfigurableEnvironment environment = applicationContext.getEnvironment(); Map<String, Object> map = new HashMap<>();
map.put("key1", "Second"); MapPropertySource mapPropertySource = new MapPropertySource("secondInitializer", map);
environment.getPropertySources().addLast(mapPropertySource); System.out.println("run secondInitializer");
} } public class ThirdInitializer implements ApplicationContextInitializer { @Override
public void initialize(ConfigurableApplicationContext applicationContext) {
ConfigurableEnvironment environment = applicationContext.getEnvironment(); Map<String, Object> map = new HashMap<>();
map.put("key1", "Third"); MapPropertySource mapPropertySource = new MapPropertySource("thirdInitializer", map);
environment.getPropertySources().addLast(mapPropertySource); System.out.println("run thirdInitializer");
} }

2.1 在 resources/META-INF/spring.factories 中配置

org.springframework.context.ApplicationContextInitializer=com.learn.springboot.initializer.FirstInitializer

2.2 在 mian 函数中添加

@SpringBootApplication
public class SpringbootApplication { public static void main(String[] args) {
// SpringApplication.run(SpringbootApplication.class, args);
SpringApplication springApplication = new SpringApplication(SpringbootApplication.class);
springApplication.addInitializers(new SecondInitializer());
springApplication.run();
} }

2.3 在配置文件中配置

context.initializer.classes=com.learn.springboot.initializer.ThirdInitializer

运行项目,查看控制台:

  .   ____          _            __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.2.5.RELEASE) run thirdInitializer
run firstInitializer
run secondInitializer

可以看到配置生效了,并且三种配置优先级不一样,配置文件优先级最高,spring.factories 其次,代码最后。

三、获取属性值

@RestController
public class HelloController { private ApplicationContext applicationContext; public HelloController(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
} @RequestMapping("/getAttributes")
public String getAttributes() {
String value = applicationContext.getEnvironment().getProperty("key1");
System.out.println(value);
return value;
} }

启动项目,访问http://localhost:8080/getAttributes 查看控制台输出:

Third

发现同名的 key,只会存在一个,并且只存第一次设置的值。

四、通过 @Order 注解修改执行顺序

注:@order 值越小,执行优先级越高

4.1 不同配置方式下,执行顺序

@Order(1)
public class SecondInitializer implements ApplicationContextInitializer {
......
} @Order(2)
public class FirstInitializer implements ApplicationContextInitializer {
......
} @Order(3)
public class ThirdInitializer implements ApplicationContextInitializer {
......
}

运行项目,查看控制台:

  .   ____          _            __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.2.5.RELEASE) run thirdInitializer
run secondInitializer
run firstInitializer

可以看到通过 @Order ** 注解是可以改变spring.factories** 和代码形式的执行顺序的,但是application.properties 配置文件的优先级还是最高的。

4.2 同一配置下,执行顺序

新建实现类

@Order(1)
public class FourthInitializer implements ApplicationContextInitializer { @Override
public void initialize(ConfigurableApplicationContext applicationContext) {
ConfigurableEnvironment environment = applicationContext.getEnvironment(); Map<String, Object> map = new HashMap<>();
map.put("key1", "Fourth"); MapPropertySource mapPropertySource = new MapPropertySource("FourthInitializer", map);
environment.getPropertySources().addLast(mapPropertySource); System.out.println("run fourthInitializer");
} }

application.properties 文件中配置

context.initializer.classes=com.learn.springboot.initializer.ThirdInitializer,com.learn.springboot.initializer.FourthInitializer

运行项目,查看控制台:

  .   ____          _            __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.2.5.RELEASE) run fourthInitializer
run thirdInitializer
run secondInitializer
run firstInitializer

可以看到同一配置方式, @Order 注解也可以起作用。

五、系统初始化器原理解析

5.1在 resources/META-INF/spring.factories 中配置实现原理

SpringApplication 初始化时通过 SpringFactoriesLoader 获取到配置在 META-INF/spring.factories 文件中的 ApplicationContextInitializer 的所有实现类.

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
......
// 设置系统初始化器
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
......
}
// 获取工厂实例对象
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
return getSpringFactoriesInstances(type, new Class<?>[] {});
}
// 获取工厂实例对象
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
// 获取类加载器
ClassLoader classLoader = getClassLoader();
// 使用名称并确保唯一以防止重复
Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
// 创建工厂实例对象
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
// 对工厂实例对象列表进行排序
AnnotationAwareOrderComparator.sort(instances);
return instances;
} // 创建工厂实例对象
private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes,
ClassLoader classLoader, Object[] args, Set<String> names) {
List<T> instances = new ArrayList<>(names.size());
for (String name : names) {
try {
Class<?> instanceClass = ClassUtils.forName(name, classLoader);
Assert.isAssignable(type, instanceClass);
Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
T instance = (T) BeanUtils.instantiateClass(constructor, args);
instances.add(instance);
}
catch (Throwable ex) {
throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);
}
}
return instances;
}

在 run 方法中回调 ApplicationContextInitializer 接口函数

public ConfigurableApplicationContext run(String... args) {
......
// 准备上下文环境注入系统初始化信息
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
......
}
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
......
// 应用初始化器
applyInitializers(context);
......
}
protected void applyInitializers(ConfigurableApplicationContext context) {
for (ApplicationContextInitializer initializer : getInitializers()) {
// 判断子类是否是 ConfigurableApplicationContext 类型
Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(),
ApplicationContextInitializer.class);
Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
// 回调 ApplicationContextInitializer接口的 initialize 方法
initializer.initialize(context);
}
}

获取初始化器列表

// 获取在 SpringApplication 构造函数中设置的初始化器列表
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.2 在 main 函数配置实现原理

在之前我们知道 SpringApplication 初始化之后,就已经把 META-INF/spring.factories 中配置的初始化实现类添加到 initializers 列表中了,然后通过 addInitializers 方法,添加自定义的实现类:

public static void main(String[] args) {
// SpringApplication.run(SpringbootApplication.class, args);
SpringApplication springApplication = new SpringApplication(SpringbootApplication.class);
springApplication.addInitializers(new ThirdInitializer());
springApplication.run();
}
public void addInitializers(ApplicationContextInitializer<?>... initializers) {
this.initializers.addAll(Arrays.asList(initializers));
}

5.3 在配置文件中配置实现原理

在配置文件中配置方式,主要通过内置的 DelegatingApplicationContextInitializer 实现的,它实现了 Order 方法,所以优先级最高。:

private int order = 0;	

@Override
public int getOrder() {
return this.order;
}

然后我们看下它的 initialize方法实现:

@Override
public void initialize(ConfigurableApplicationContext context) {
// 获取上下文环境变量
ConfigurableEnvironment environment = context.getEnvironment();
// 从上下文环境变量中获取指定初始化类列表
List<Class<?>> initializerClasses = getInitializerClasses(environment);
if (!initializerClasses.isEmpty()) {
// 应用初始化器
applyInitializerClasses(context, initializerClasses);
}
}

从上下文环境变量获取指定的属性名,并实例化对象

private static final String PROPERTY_NAME = "context.initializer.classes";

private List<Class<?>> getInitializerClasses(ConfigurableEnvironment env) {
// 从上下文环境变量获取指定的属性名
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);
}
}

ApplicationContextInitializer的理解和使用的更多相关文章

  1. SpringBoot之ApplicationContextInitializer的理解和使用

    一. ApplicationContextInitializer 介绍 首先看spring官网的介绍: 翻译一下: 用于在spring容器刷新之前初始化Spring ConfigurableAppli ...

  2. SpringBoot启动流程分析(一):SpringApplication类初始化过程

    SpringBoot系列文章简介 SpringBoot源码阅读辅助篇: Spring IoC容器与应用上下文的设计与实现 SpringBoot启动流程源码分析: SpringBoot启动流程分析(一) ...

  3. Spring Cloud 中自定义外部化扩展机制原理及实战

    Spring Cloud针对Environment的属性源功能做了增强, 在spring-cloud-contenxt这个包中,提供了PropertySourceLocator接口,用来实现属性文件加 ...

  4. 深入理解SpringCloud之引导程序应用上下文

    tips:我希望通过这篇文章来给对于bootstrap还不理解的朋友带来帮助.当然这篇文章不仅仅是讲解知识,我更希望给广大朋友带来学习与理解官方文档的一种思路.阅读本文前,建议大家对SpringBoo ...

  5. SpringBoot 版本升级后报错 Cannot instantiate interface org.springframework.context.ApplicationContextInitializer

    本篇博客纯粹讲我遇到这个问题的解决以及思考,如果你想知道解决方法,可以直接看正确解决方案部分.因为是前端写的,所以可能有些明显的内容很容易就看出来了. 首先:升级后更新其他依赖,以及Applicati ...

  6. 一些SpringBoot的初步理解

    SpringBoot SpringBoot作为近几年很火的微服务框架,只需要简单的几个依赖,少量的配置,就可以使用它快速搭建一个轻量级的微服务,优点是简单.快速.大道至简,缺点是真的太单一,不适于项目 ...

  7. SpringBoot深入理解

    SpringBoot深入理解 项目打包SpringBoot启动过程 当使用打包时,会下载org-springframework-boot-loader的jar,并且不会放在lib存放的第三方jar包文 ...

  8. 理解CSS视觉格式化

    前面的话   CSS视觉格式化这个词可能比较陌生,但说起盒模型可能就恍然大悟了.实际上,盒模型只是CSS视觉格式化的一部分.视觉格式化分为块级和行内两种处理方式.理解视觉格式化,可以确定得到的效果是应 ...

  9. 彻底理解AC多模式匹配算法

    (本文尤其适合遍览网上的讲解而仍百思不得姐的同学) 一.原理 AC自动机首先将模式组记录为Trie字典树的形式,以节点表示不同状态,边上标以字母表中的字符,表示状态的转移.根节点状态记为0状态,表示起 ...

随机推荐

  1. python数据类型:元组

    python数据类型:元组 python的元组与列表类似,但是元组的元素不能修改 元组使用小括号,列表使用大括号 元组创建简单,只需要在括号中添加元素,使用逗号隔开 创建元组: tup1 = (50, ...

  2. 安装oracle 11g 客户端,检查过程中报物理内存不足的解决

    今早接到同事电话,说安装oracle 11g客户端的时候,在检查先决条件的时候,报错,说内存不足,但是本机的内存是2G,肯定够用:如图: 找了一圈,原来Oracle执行先决条件检查是依赖c$共享,很多 ...

  3. group compare vs pair compare

    成对总体检验是令y1=x11-x12:y2=x21-x22等,令新的随机变量y去做假设检验.此方法适用于排除物理因素影响,对差异更敏感,所以适用于小样本.而使用两个总体均值比较的方法适用于大样本.

  4. 良知VS野心,苹果为何要翻新手机?

    前不久,苹果在大中华区推出了iPhone和iPad换机服务,消息一经发出便引发了果粉们的狂欢,那些丢弃在抽屉里的iPhone4S们看上去终于有着落了,也更坚定了"我是果粉,我骄傲" ...

  5. SSM框架三分钟搞定分页查询

    使用的国产第三方jar   pagehelper 里面的基本属性值 //当前页 private int pageNum; //每页的数量 private int pageSize; //当前页的数量 ...

  6. Python---13靠谱的Pycharm安装详细教程

    昨天自学廖雪峰老师的python课程时,用的sublime编辑器,在命令行模式进行的输出,输出结果一直报错,说Python版本有问题,但在版本是满足要求的.最后在同事的电脑上运行了一下,是可以正常输出 ...

  7. 支持向量机 SVM - Wenjing

    概念 将向量映射到一个更高维的空间里,在这个空间里建立有一个最大间隔超平面.在分开数据的超平面的两边建有两个互相平行的超平面,分隔超平面使两个平行超平面的距离最大化.假定平行超平面间的距离或差距越大, ...

  8. 3名程序员被抓!开发“万能钥匙”APP,撬走3个亿

    来自:程序员头条 报道 又有 3 名程序员被抓!开发"万能钥匙"APP,撬走 3 亿! 前几天,据央视新闻报道,上海公安机关接到共享单车企业报案,随后破获了一起共享单车万能解锁 A ...

  9. 杂记:OSX 安装openssl

    因为工作中要用到openssl中提供的MD5.SHA等摘要算法,通过brew install openssl安装的openssl在C文件中找不到相应的头文件.按照网上的教程各种修改之后还是找不到相应的 ...

  10. 遗弃.Forsaken.2015.BluRay.720p.x264.DTS-beAst

    ◎译 名 遗弃/落日孤影(台)/赎罪◎片 名 Forsaken◎年 代 2015◎产 地 加拿大/法国/美国◎类 别 剧情/西部◎语 言 英语◎上映日期 2015-09-16(多伦多电影节)/2016 ...