《Spring Boot源码博客》
ApplicationContextInitializer接口的文档是这么写的:
1、ApplicationContextInitializer是一个回调接口,用于在ConfigurableApplicationContext#refresh()执行刷新之前初始化ConfigurableApplicationContext。SpringApplication#prepareContext()方法会执行ApplicationContextInitializer实现类的initialize方法。
2、通常在需要对应用程序上下文进行一些编程初始化的Web应用程序中使用,例如对ConfigurableApplicationContext#getEnvironment()注册属性源或者激活配置文件。
3、鼓励系统初始化器去实现org.springframework.core.Ordered Ordered接口或者使用@Order注解,以便在调用之前对实例进行排序。
若不举例说明,很难理解ApplicationContextInitializer接口的文档说的是什么意思。那现在就开始介绍系统初始化器的3种使用方式吧。
第一种:使用spring.factories配置
1、新建FirstInitializer实现ApplicationContextInitializer接口
/**
* 使用@Order注解(第3点)
*/
@Order(1)
public class FirstInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
/**
* 在resources目录下新建/META-INF/spring.factories目录与文件。
* 在resources/META-INF/spring.factories配置:
* org.springframework.context.ApplicationContextInitializer=com.example.springbootdemo.FirstInitializer
*
* spring.factories的配置规则是: 接口全名=实现类全名
*/
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
/**
* 本方法代码是添加属性 key1=value1。
*
* 可通过以下方式获取添加的属性:
* @Value("${key1}")
* String key1;
*/
ConfigurableEnvironment environment = applicationContext.getEnvironment();
Map<String, Object> map = new HashMap<>();
map.put("key1", "value1");
// 新建属性源,并添加属性源。(第2点)
MapPropertySource mps = new MapPropertySource("firstInitializer", map);
environment.getPropertySources().addLast(mps);
System.out.println("#############FirstInitializer.initialize 运行");
}
}
2、在resources目录下新建/META-INF/spring.factories目录与文件
在spring.factories中添加
org.springframework.context.ApplicationContextInitializer=com.example.springbootdemo.FirstInitializer
3、获取属性值的方式如下:
@Value("${key1}")
String key1;
4、启动工程,就能获取值了。
5、不使用@Order,通过实现Ordered接口实现排序。新建FirstOrderedInitializer.java
/**
* 通过实现Ordered接口实现排序
*/
public class FirstOrderedInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
System.out.println("#############FirstOrderedInitializer.initialize 运行");
}
// 返回排序值
@Override
public int getOrder() {
return 11;
}
}
6、在spring.factories中添加
org.springframework.context.ApplicationContextInitializer=com.example.springbootdemo.FirstInitializer,\
com.example.springbootdemo.FirstOrderedInitializer
7、运行程序,控制台输出
#############FirstInitializer.initialize 运行
#############FirstOrderedInitializer.initialize 运行
FirstInitializer使用@Order注解排序值是1,FirstOrderedInitializer#getOrder()返回11。由此可知:排序值越小越先执行。
第二种:硬编码方式 addInitializers(new SecondInitializer());
1、新建SecondInitializer.java
@Order(2)
public class SecondInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
ConfigurableEnvironment environment = applicationContext.getEnvironment();
Map<String, Object> map = new HashMap<>();
map.put("key2", "value2");
MapPropertySource mps = new MapPropertySource("secondInitializer", map);
environment.getPropertySources().addLast(mps);
System.out.println("#############SecondInitializer.initialize 运行");
}
}
2、修改启动类,通过代码添加SecondInitializer
@SpringBootApplication
public class SpringBootDemoApplication {
public static void main(String[] args) {
//SpringApplication.run(SpringBootDemoApplication.class, args);
// 第2中添加系统初始化器的方式
SpringApplication springApplication = new SpringApplication(SpringBootDemoApplication.class);
springApplication.addInitializers(new SecondInitializer());
springApplication.run(args);
}
}
第三种:在application.properties配置context.initializer.classes
1、新建ThirdInitializer.java
@Order(3)
public class ThirdInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
ConfigurableEnvironment environment = applicationContext.getEnvironment();
Map<String, Object> map = new HashMap<>();
map.put("key3", "value3");
MapPropertySource mps = new MapPropertySource("thirdInitializer", map);
environment.getPropertySources().addLast(mps);
System.out.println("#############ThirdInitializer.initialize 运行");
}
}
2、在application.properties中配置
context.initializer.classes=com.example.springbootdemo.ThirdInitializer
3、启动工程,控制台有以下输出
#############ThirdInitializer.initialize 运行
#############FirstInitializer.initialize 运行
#############SecondInitializer.initialize 运行
#############FirstOrderedInitializer.initialize 运行
ThirdInitializer的注解是@Order(3),但是ThirdInitializer比FirstInitializer、SecondInitializer先运行。是不是使用context.initializer.classes配置的初始化器就会先运行呢?后面会回答这问题。
源码讲解
spring boot 版本是2.2.4.RELEASE。
先把启动类SpringBootDemoApplication.java的代码还原为
SpringApplication.run(SpringBootDemoApplication.class, args);
使用最常规的启动方式来分析spring boot 的源码。
1、进入run方法内,直到看见如下代码:
// 源码位置org.springframework.boot.SpringApplication#run(java.lang.Class<?>[], java.lang.String[])
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
}
可粗略的得出一个结论,spring boot 使用一句代码 SpringApplication.run(SpringBootDemoApplication.class, args); 启动工程,本质上是创建SpringApplication实例,再运行实例的run方法。run方法执行完,工程就启动完成了。
1.1、继续debug,进入SpringApplication的构造函数中
// 源码位置 org.springframework.boot.SpringApplication#SpringApplication(org.springframework.core.io.ResourceLoader, java.lang.Class<?>...)
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();
// 这行代码就是设置初始化器
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
1.1.1、setInitializers方法源码如下:
// 源码位置 org.springframework.boot.SpringApplication#setInitializers
public void setInitializers(Collection<? extends ApplicationContextInitializer<?>> initializers) {
/**
* 系统初始化器可以有多个,形成一个系统初始化器列表。
* 在创建SpringApplication对象时,将系统初始化器列表赋值给SpringApplication的initializers属性。
*/
this.initializers = new ArrayList<>(initializers);
}
1.1.2、getSpringFactoriesInstances(ApplicationListener.class)是获取系统初始化器的函数
进入方法体中debug
// 源码位置 org.springframework.boot.SpringApplication#getSpringFactoriesInstances(java.lang.Class<T>, java.lang.Class<?>[], java.lang.Object...)
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<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
1.1.2.1、ClassLoader classLoader = getClassLoader();
getClassLoader()返回结果是AppClassLoader实例。
Java自带了3个类加载器:BootstrapClassLoader、ExtClassLoader、AppClassLoader。AppClassLoader会加载classpath路径下jar包和目录的class文件。我们自己编写的代码以及代码依赖的第三方jar包通常都是由它来加载的。
可运行System.getProperty("java.class.path"),查看当前工程的classpath是什么。
1.1.2.2、Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
type是ApplicationContextInitializer接口,这句代码是获取type接口实现类的类全名集合。进入到SpringFactoriesLoader.loadFactoryNames方法内部。
// 源码位置 org.springframework.core.io.support.SpringFactoriesLoader#loadFactoryNames
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
String factoryTypeName = factoryType.getName();
/**
* 重点代码是 loadSpringFactories(classLoader)。
* 先讲结论,loadSpringFactories(classLoader)返回一个LinkedMultiValueMap对象,此对象一个key对应多个value。
* key是接口全类名,value是接口的实现类全类名,value可以有多个。
*/
return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}
debug到loadSpringFactories(classLoader)内部
// 源码位置 org.springframework.core.io.support.SpringFactoriesLoader#loadSpringFactories
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}
try {
/**
* FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
* 通过AppClassLoader加载Classpath下的META-INF/spring.factories
* 当前工程和依赖的第三方jar包中的META-INF/spring.factories都会被加载进来
*/
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
// resource是包含了spring.factories文件路径的资源对象
UrlResource resource = new UrlResource(url);
// 使用spring.factories中配置的键值对创建Properties对象
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
// 循环properties的键值对
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryTypeName = ((String) entry.getKey()).trim();
for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
/**
* factoryTypeName是spring.factories中配置的key,比如:ApplicationContextInitializer接口全类名
* factoryImplementationName是key对应的value,比如:ApplicationContextInitializer实现类FirstInitializer的全类名
*/
result.add(factoryTypeName, factoryImplementationName.trim());
}
}
}
cache.put(classLoader, result);
/**
* result是一个LinkedMultiValueMap。key是接口全名,value是接口的实现类全类名,value可以是多个。
* 需要注意的是:
* 1、AppClassLoader会获取Classpath下所有jar包的spring.factories文件
* 2、并将spring.factories中所有的配置添加到result中
* 基于以上两点,就不难理解为什么要用缓存了
* cache.put(classLoader, result); 、 MultiValueMap<String, String> result = cache.get(classLoader);
* AppClassLoader把classpath下所有的spring.factories读取到缓存中。下次要用spring.factories配置时,从缓存中拿就好了
*/
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
在此总结下 Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader)); 这行代码的作用。大致分为以下2个小步骤:
1、使用AppClassLoader获取Classpath下的spring.factories文件的路径
2、读取spring.factories并创建properties对象
3、将type接口(本文中type是ApplicationContextInitializer)实现类的类全名返回。第三方jar包spring.factories中配置的ApplicationContextInitializer实现类的全类名也会被读取并返回,所以结果是Set<String>类型。
1.1.2.3、List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
获得了系统初始化的全类名集合,就能创建系统初始化器实例集合了,源码如下:
// 源码位置 org.springframework.boot.SpringApplication#createSpringFactoriesInstances
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 {
// AppClassLoader使用类全名把类加载到jvm内存中
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;
}
1.1.2.4、AnnotationAwareOrderComparator.sort(instances); 这是对系统初始化器实例集合进行排序。
debug到如下代码内
// 源码位置 org.springframework.core.OrderComparator#doCompare()
private int doCompare(@Nullable Object o1, @Nullable Object o2, @Nullable OrderSourceProvider sourceProvider) {
boolean p1 = (o1 instanceof PriorityOrdered);
boolean p2 = (o2 instanceof PriorityOrdered);
if (p1 && !p2) {
return -1;
}
else if (p2 && !p1) {
return 1;
}
/**
* getOrder方法很复杂,本文只关注getOrder方法的两个作用。
* 1、获取@Order注解值
* 2、获取Ordered接口getOrder方法返回值
*/
int i1 = getOrder(o1, sourceProvider);
int i2 = getOrder(o2, sourceProvider);
// 排序规则是升序
return Integer.compare(i1, i2);
}
在以下代码中打上断点,运行程序debug
com.example.springbootdemo.FirstOrderedInitializer#getOrder()
// 调用Ordered实现类的getOrder()方法,获取排序值
return 2;
org.springframework.core.annotation.OrderUtils#findOrder()
// getOrder方法获取@Order注解的值
MergedAnnotation<Order> orderAnnotation = annotations.get(Order.class);
if (orderAnnotation.isPresent()) {
return orderAnnotation.getInt(MergedAnnotation.VALUE);
}
至此,系统初始化器的初始化代码就讲完了。重点是getSpringFactoriesInstances方法,下面对此方法做总结:
// 源码位置 org.springframework.boot.SpringApplication#getSpringFactoriesInstances(java.lang.Class<T>, java.lang.Class<?>[], java.lang.Object...)
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
// getClassLoader()返回AppClassLoader
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;
}
FirstInitializer添加到SpringApplication.initializers的原理已经讲清楚了。
SecondInitializer添加给SpringApplication.initializers的方式就很简单了,springApplication.addInitializers(new SecondInitializer()); 的源码如下:
// 源码位置 org.springframework.boot.SpringApplication#addInitializers
public void addInitializers(ApplicationContextInitializer<?>... initializers) {
this.initializers.addAll(Arrays.asList(initializers));
}
目前遗留下两个问题:
1、SecondInitializer:addAll是将SecondInitializer添加到this.initializers的末尾,排序位置不对。
2、ThirdInitializer通过application.properties配置,还没被添加到this.initializers中。
带着这两个疑问,我们来探究 new SpringApplication(primarySources).run(args); 的run方法,即SpringApplication的运行阶段。
使用 SpringApplication.run(SpringBootDemoApplication.class, args); 启动程序。在源码中是执行 new SpringApplication(primarySources).run(args); 这句代码跟前面介绍第二种配置系统初始化器的代码很相似,为了配置SecondInitializer,需要把启动类的代码改成
@SpringBootApplication
public class SpringBootDemoApplication {
public static void main(String[] args) {
//SpringApplication.run(SpringBootDemoApplication.class, args);
// 第2中添加系统初始化器的方式
SpringApplication springApplication = new SpringApplication(SpringBootDemoApplication.class);
springApplication.addInitializers(new SecondInitializer());
springApplication.run(args); // 这句代码打上断点调试
}
}
在 springApplication.run(args); 这句代码打上断点,启动工程、调试。
debug到run方法内
// 源码位置 org.springframework.boot.SpringApplication.run(java.lang.String...)
public ConfigurableApplicationContext run(String... args) {
/**
* run方法很复杂,本文作为spring boot源码系列的第一篇博客,仅关注系统初始化器相关代码。其他代码忽略
* prepareContext方法内会运行系统初始化器
*/
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
// ApplicationContextInitializer接口文档第三点
refreshContext(context);
}
debug到prepareContext方法中
// 源码位置 org.springframework.boot.SpringApplication.prepareContext
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
// 应用初始化器
applyInitializers(context);
}
applyInitializers方法源码:
// 源码位置 org.springframework.boot.SpringApplication#applyInitializers
protected void applyInitializers(ConfigurableApplicationContext context) {
/**
* getInitializers()获取系统初始化器,并且对系统系统初始化器再次排序,解决了SecondInitializer的排序问题
* 循环系统初始化器,并执行系统初始化器的initialize方法
*/
for (ApplicationContextInitializer initializer : getInitializers()) {
Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(),
ApplicationContextInitializer.class);
Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
initializer.initialize(context);
}
}
1、getInitializers()获取系统初始化器,并且对系统系统初始化器再次排序
// 源码位置 org.springframework.boot.SpringApplication#asUnmodifiableOrderedSet
private static <E> Set<E> asUnmodifiableOrderedSet(Collection<E> elements) {
List<E> list = new ArrayList<>(elements);
// 再次对系统初始化器集合排序
list.sort(AnnotationAwareOrderComparator.INSTANCE);
return new LinkedHashSet<>(list);
}
2、getInitializers()获得系统初始化器集合,排在第一位的系统初始化器是DelegatingApplicationContextInitializer(委派系统初始化器,Order值是0),它会将初始化操作委派给环境属性context.initializer.classes指定的初始化器。
DelegatingApplicationContextInitializer.initialize方法源码如下
// 源码位置org.springframework.boot.context.config.DelegatingApplicationContextInitializer#initialize
public void initialize(ConfigurableApplicationContext context) {
// 获取环境变量
ConfigurableEnvironment environment = context.getEnvironment();
// 获取context.initializer.classes配置的初始化器集合
List<Class<?>> initializerClasses = getInitializerClasses(environment);
if (!initializerClasses.isEmpty()) {
// 执行初始化器的initialize方法
applyInitializerClasses(context, initializerClasses);
}
}
2.1 getInitializerClasses(environment); 源码分析
// 源码位置 org.springframework.boot.context.config.DelegatingApplicationContextInitializer#getInitializerClasses
private List<Class<?>> getInitializerClasses(ConfigurableEnvironment env) {
// PROPERTY_NAME = "context.initializer.classes"
String classNames = env.getProperty(PROPERTY_NAME);
List<Class<?>> classes = new ArrayList<>();
if (StringUtils.hasLength(classNames)) {
// 通过","分隔context.initializer.classes的值
for (String className : StringUtils.tokenizeToStringArray(classNames, ",")) {
// getInitializerClass(className)使用AppClassLoader加载类
classes.add(getInitializerClass(className));
}
}
return classes;
}
2.2、applyInitializerClasses(context, initializerClasses);源码分析
// 源码位置 org.springframework.boot.context.config.DelegatingApplicationContextInitializer#applyInitializerClasses
private void applyInitializerClasses(ConfigurableApplicationContext context, List<Class<?>> initializerClasses) {
Class<?> contextClass = context.getClass();
List<ApplicationContextInitializer<?>> initializers = new ArrayList<>();
// initializerClasses是context.initializer.classes指定的类集合
for (Class<?> initializerClass : initializerClasses) {
// 实例化系统初始化器
initializers.add(instantiateInitializer(contextClass, initializerClass));
}
// 应用系统初始化器
applyInitializers(context, initializers);
}
2.2.1 applyInitializers(context, initializers); 源码分析
// 源码位置 org.springframework.boot.context.config.DelegatingApplicationContextInitializer#applyInitializers
private void applyInitializers(ConfigurableApplicationContext context,
List<ApplicationContextInitializer<?>> initializers) {
// 先排序,这里是对context.initializer.classes配置的系统初始化器进行排序
initializers.sort(new AnnotationAwareOrderComparator());
for (ApplicationContextInitializer initializer : initializers) {
// 执行初始化器的initialize方法,会进入 ThirdInitializer#initialize 方法中
initializer.initialize(context);
}
}
ThirdInitializer就被调用了。现在对DelegatingApplicationContextInitializer(委派系统初始化器)做个总结:
1、DelegatingApplicationContextInitializer的排序值是0,比FirstInitializer、SecondInitializer的排序值小,最先被调用。
2、DelegatingApplicationContextInitializer实例化context.initializer.classes配置的系统初始化器,并运行这些系统初始化器的initialize方法。所以context.initializer.classes配置的系统初始化器会先运行。
3、DelegatingApplicationContextInitializer的排序值是0,若把FirstInitializer的排序值设置为小于0,则FirstInitializer会先运行。
讲完了ThirdInitializer的实例化、运行过程。在回到 org.springframework.boot.SpringApplication#applyInitializers方法中,继续讲解FirstInitializer、FirstOrderedInitializer、SecondInitializer的运行过程
// 源码位置 org.springframework.boot.SpringApplication#applyInitializers
protected void applyInitializers(ConfigurableApplicationContext context) {
/**
* 循环处理getInitializers()返回的系统初始化器,
* 排在第一位的是DelegatingApplicationContextInitializer,前面已经讲了此初始化器的运行过程
* 后面几次循环获得的initializer包含FirstInitializer、FirstOrderedInitializer、SecondInitializer
* initializer.initialize(context);就直接运行我们自定义的系统初始化器了
*/
for (ApplicationContextInitializer initializer : getInitializers()) {
Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(),
ApplicationContextInitializer.class);
Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
initializer.initialize(context);
}
}
好了,spring boot 2系统初始化器的使用与源码都讲完了,后面我会继续更新 spring boot 源码系列 ,欢迎大家来一起交流。