Springboot之应用初始化——ApplicationContextInitializer.class
1. 先介绍ApplicationContextInitializer.class
ApplicationContextInitializer是Spring提供的用于应用启动时回调的接口,需要实现此接口的initializeinitialize(ConfigurableApplicationContext applicationContext)方法。当Spring启动(即执行run方法)并初始化环境配置以后,完成应用初始化以前,会回调所有ApplicationContextInitializer的子类(当然是已经配置了的子类)。
2. 怎么用呢?
2.1. 实现接口
这没啥好说的,就看示例吧:
package com.jwb.demo.initializer;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.annotation.Order;
@Order(2) // 如果有多个实体类,可以配置的顺序,此注解非必须的
public class MyApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
public MyApplicationContextInitializer() {
System.out.println("MyApplicationContextInitializer 构造方法。");
}
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
System.out.println("MyApplicationContextInitializer 的 initialize()方法被回调了。");
}
}
2.2. 将实现好的类配置到环境中
一般看源码,一边说配置方法,做到知其然更知其所以然。
话不多说,上源码:
2.2.1. spring加载默认配置中指定的实体类
先看看ApplicationContextInitializer的子类何时被实例化的。
public class SpringApplication {
// SpringApplication 的构造方法
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 = deduceWebApplicationType();
/// 在这里实例化了ApplicationContextInitializer的子类/
/// 在这里实例化了ApplicationContextInitializer的子类/
/// 在这里实例化了ApplicationContextInitializer的子类/
// getSpringFactoriesInstances()方法是,在文件META-INF/spring.factories读取配置好的实体的全限类名,并将这些类一一的实例化
setInitializers((Collection) getSpringFactoriesInstances( ApplicationContextInitializer.class));
/// 在这里实例化了ApplicationContextInitializer的子类/
/// 在这里实例化了ApplicationContextInitializer的子类/
/// 在这里实例化了ApplicationContextInitializer的子类/
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
// 添加Initializer到环境中的函数
public void setInitializers(
Collection<? extends ApplicationContextInitializer<?>> initializers) {
// private List<ApplicationContextInitializer<?>> initializers; 这是成员变量
this.initializers = new ArrayList<>();
this.initializers.addAll(initializers); // 实际上就把这些实例化后的Initializer添加到了List添中
}
}
2.2.1.1. 小结
看了源码过后,就搞明白了什么时候实例化这些Initializer并保存到了哪里去了。那么怎么配置就很好理解了,相信不用教都会了。
spring在此种情况下加载的实体类,是配置在文件META-INF/spring.factories中配置好的,在文件中加入配置org.springframework.context.ApplicationContextInitializer=xxx.xxx.xxx.myApplicationContextInitializer
。如果想了解加载过程,可以看看我的另外一篇文章,里边有说到关于getSpringFactoriesInstances()方法是如何加载实体类的。链接
2.2.2. 在项目配置(application.properties)文件中添加配置
先不说怎么配置,还是先看看,springboot是如何从application.properties加载到你配置的实体类的呢?
结合上文提到的,应用会在实例化SpringApplication的时候,加载文件META-INF/spring.factories中配置好的默认实体类,那么就有这么一个实体类,负责加载application.properties文件中所配置的实体类。
下面还是通过源码解读,看看实体类怎么加载的,实例化以后又做了那些事呢?
META-INF/spring.factories文件中的部分内容如下:
我们来看DelegatingApplicationContextInitializer这个实体类,就它,就这个了,就它负责加载application.properties中配置的Initializer实体类的。上源码看看:
public class DelegatingApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {
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()) {
applyInitializerClasses(context, initializerClasses);
}
}
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);
}
}
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));
}
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());
for (ApplicationContextInitializer initializer : initializers) {
initializer.initialize(context);
}
}
public void setOrder(int order) {
this.order = order;
}
@Override
public int getOrder() {
return this.order;
}
}
2.2.2.1. 小结
什么?代码很长,看懵了!没关系,总结一下就好了。
先看看initialize()方法,所有的Initializer都必须实现的方法,spring会在初始化过程中调用的。那么就从这里开始看吧。
主要看getInitializerClasses()方法, 此方法中作了两件事情,读取配置文件和应用初始化器(Initializer)。
2.2.2.1.1. 读取配置文件(哈哈,重点来了,这里就会说到怎么配置)
String classNames = env.getProperty(PROPERTY_NAME);
这里看出,在读取配置文件中key为context.initializer.classes的值。嘿嘿,对,你的直觉是对的,在application.properties中加入这样的配置
context.initializer.classes=com.jwb.demo.initializer.MyApplicationContextInitializer
就可以将你的自定义的实现类配置上了,多个Initializer的全限类名可以用英文的逗号分隔","。
2.2.2.1.2. 运用Initializer
那么实例化好了这些Initializer之后呢,来看看
applyInitializers()方法的内部关键代码
for (ApplicationContextInitializer initializer : initializers) {
initializer.initialize(context);
}
刚实例化好的,我们配置的自定义Initializer,都便利了,并调用了initialize()方法,没错,就是你必须且唯一实现的那个方法。
2.2.3. 在run方法之前添加
如果你理解了上文说到的第一种配置方法,那么这种方法添加Initializer简直就是太好理解了。来,直接上示例
package com.jwb.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import com.jwb.demo.initializer.MyApplicationContextInitializer;
@SpringBootApplication
public class GenDemoApplication {
public static void main(String[] args) {
SpringApplication springApplication = new SpringApplication(GenDemoApplication.class);
springApplication.addInitializers(new MyApplicationContextInitializer()); // 添加Initializer
springApplication.run(args);
}
}
咱都知道,所有的Initializer的initialize()方法,都是再初始化过程中被调用的,也就是再springApplication.run(args);
这句代码执行的时候被调用的。
那么,再拿被执行之前添加到SpringApplication中的成员变量initializers里就可以了,还记得吗,成员变量private List<ApplicationContextInitializer<?>> initializers;
即使一个List。
看看源码,是不是这样的:
public void addInitializers(ApplicationContextInitializer<?>... initializers) {
this.initializers.addAll(Arrays.asList(initializers));
}
简单吧~~~~~~~~~~~~~~~~~~~~~ !!!!
3. 关于order
我们可以看到,DelegatingApplicationContextInitializer还实现了Order接口,这个Order是spring在多个默认Initializer中执行循序的排序,这个类的order是0,数字越小就越先执行。那么,我们写的实现类中,如果@Order注解是0,会不会导致先执行我们自定义的application文件中定义的Initializer之后,才执行这个类呢?
答案是:----------绝对不会的 ----------- 。为什么?因为儿子绝对不会再父亲之前出生的,哈哈!多体会体会,你就能明白了。
咱自定义的Initializer的order顺序,只会影响到所有由DelegatingApplicationContextInitializer从application文件中加载的类的执行顺序。
但是,但是,但是。。。。。。。
如果你用META-INF/spring.factories的配置方式,嘿嘿,就会有可能先执行咱自定义的Initializer。