ApplicationContextInitializer的理解和使用
一、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 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);
}
}
获取初始化器列表
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 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);
}
}
|