文章目录
前言
本节就来了解一下ApplicationContextInitializer的使用、回调流程,会跟进源码看。
Spring Boot Version:2.1.7/2.1.9
ApplicationContextInitializer简介
SpringBoot在刷新上下文之前,会遍历所有配置文件中配置的ApplicationContextInitializer实现类并调用其initialize方法,用户可通过实现ApplicationContextInitializer接口+覆写initialize方法从而在刷新上下文之前做一些事情,比如:初始化一些自定义上下文属性。
实验代码
java代码
为了加深印象、断点追踪源码,我需要写一些实验代码,以下列出一些核心代码or配置,相信有springBoot使用经验的人能秒懂。
FirstInitializer.java
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;
import java.util.HashMap;
import java.util.Map;
@Order(1)
public class FirstInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
ConfigurableEnvironment environment = applicationContext.getEnvironment();
Map<String, Object> map = new HashMap<>();
map.put("key1", "value1");
MapPropertySource mapPropertySource = new MapPropertySource("firstInitializer", map);
environment.getPropertySources().addLast(mapPropertySource);
System.out.println("run firstInitializer");
}
}
SecondInitializer.java
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;
import java.util.HashMap;
import java.util.Map;
@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 mapPropertySource = new MapPropertySource("secondInitializer", map);
environment.getPropertySources().addLast(mapPropertySource);
System.out.println("run secondInitializer");
}
}
ThirdInitializer.java
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;
import java.util.HashMap;
import java.util.Map;
@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 mapPropertySource = new MapPropertySource("thirdInitializer", map);
environment.getPropertySources().addLast(mapPropertySource);
System.out.println("run thirdInitializer");
}
}
- 记住ThirdInitializer的@Order(3)
DemoService.java
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
@Component
public class TestService implements ApplicationContextAware {
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
/**
* 返回上下文的环境变量/属性
*/
public String test(String property) {
return applicationContext.getEnvironment().getProperty(property);
}
}
DemoController.java
import com.wenjie.sb2.service.DemoService;
import com.wenjie.sb2.service.TestService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.Optional;
@Controller
@RequestMapping("/demo")
public class DemoController {
@Autowired
private TestService testService;
@RequestMapping("test")
@ResponseBody
public String test() {
String string = testService.test("key1") + "\n"
+ testService.test("key2") + "\n"
+ testService.test("key3");
return string;
}
}
TestDemoApplication.java
import com.wenjie.sb2.initializer.SecondInitializer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Sb2Application {
public static void main(String[] args) {
SpringApplication springApplication = new SpringApplication(TestDemoApplication.class);
springApplication.addInitializers(new SecondInitializer());
springApplication.run(args);
}
}
配置文件
spring.factories
org.springframework.context.ApplicationContextInitializer=com.wenjie.sb2.initializer.FirstInitializer
- 第二种设置ApplicationContextInitializer方法。
- 第一种设置方法在上面的main函数中。
- 为什么在这个目录?等下跟进源码就知道了。
application.properties
server.port=8080
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.url=jdbc:mysql://192.168.129.133:3306/test
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
mybatis.configuration.map-underscore-to-camel-case=true
logging.pattern.console=[%thread] %-5level %logger{50} - %msg%n
context.initializer.classes=com.wenjie.sb2.initializer.ThirdInitializer
- 第三种设置ApplicationContextInitializer方法。
输出结果
启动应用,控制台、访问Controller输出结果如下:
控制台输出
- 注意,thirdInitializer的注解是@Order(3),但却是最先完成调用的,后面会解释为什么。
浏览器访问接口,成功返回自定义属性:
下面就围绕实验代码、输出结果一步步跟进源码。
跟进源码
读取spring.factories配置
读取spring.factories配置最先拿到FirstInitializer的包地址。当然,除了用户自定义的spring.factories外,springboot也自带了一些spring.factories,如下图:
- 说明除了SpringBoot也有自带的Initializer,后面我们需要查看这里面的其中一个原生配置。
下面就从源码体验一下这个加载配置文件的过程。
启动应用时,首先是要构造SpringApplication,那我们就直接找到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();
}
跟进getSpringFactoriesInstances方法:
org.springframework.boot.SpringApplication#getSpringFactoriesInstances(java.lang.Class
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
return getSpringFactoriesInstances(type, new Class<?>[] {});
}
跟进getSpringFactoriesInstances方法,此处【坐标1】:
org.springframework.boot.SpringApplication#getSpringFactoriesInstances(java.lang.Class
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
// 获取/构造ClassLoader
ClassLoader classLoader = getClassLoader();
// Use names and ensure unique to protect against duplicates
// 读取完配置文件后,将要加载的类的包路径保存到set集合中
Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
// 利用classLoader去加载符合条件的类
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
// 根据@Order注解的值排序
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
下面先跟进SpringFactoriesLoader.loadFactoryNames方法:
org.springframework.core.io.support.SpringFactoriesLoader#loadSpringFactories
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}
跟进loadSpringFactories方法,就能看到比较完整的加载配置逻辑了:
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 {
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();
UrlResource resource = new UrlResource(url);
// 加载配置
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
// 遍历加载到的配置
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryClassName = ((String) entry.getKey()).trim();
// 以,分割值,逐个保存
for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
result.add(factoryClassName, factoryName.trim());
}
}
}
// 缓存集合
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
- 相信经历过只用serverlet写java项目(要自己写代码加载配置)的人来说,上面的代码逻辑是没啥难度的,只是spring二次封装过的工具类而已。
看看最终返回的集合,里面就包含了FirstInitializer的包地址:
来到这里不妨再看看FACTORIES_RESOURCE_LOCATION属性到底是啥,看完后相信你就明白为什么spring.factories要放在META-INF下了:
/**
* The location to look for factories.
* <p>Can be present in multiple JAR files.
*/
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
最后将返回的对象保存到一个ArrayList中,完成构造:
org.springframework.boot.SpringApplication#setInitializers
public void setInitializers(Collection<? extends ApplicationContextInitializer<?>> initializers) {
this.initializers = new ArrayList<>();
this.initializers.addAll(initializers);
}
- 请记住这个initializers,后面会用到。
- 另外补充一点:SecondInitializer的对象也是被添加到这个initializers中的,不信你可以跟进一下实验代码addInitializers方法,我就不这里赘述了。
回顾整个读取spring.factories的流程,画一张流程图大概就是下面这个样子:
好了,现在我们已经知道读取spring.factories的整个流程了,而且还顺利获取到FirstInitializer的包路径,接下来就是要拿这个包路径来初始化了。
构造Initializers对象
视角重新转回到上面【坐标1】的getSpringFactoriesInstances方法,继续跟进createSpringFactoriesInstances方法:
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 {
// 使用类加载器构造对象
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;
}
- 可见就是遍历前面读取到的包路径,利用反射机制去构造对象。
构造完之后,就存储到List集合中并返回,最后调用AnnotationAwareOrderComparator.sort(instances);
进行排序。
不要忘了Initializer的作用是刷新上下文之前做某些初始化操作,现在我们还仅仅是完成了Initializer的对象构造而已,还没有调用实验代码覆写的initialize方法。
关于SecondInitializer、ThirdInitializer的构造,会在下文遇到。
回调initialize方法
回调initialize方法,是在SpringApplication完成构造之后执行的,所以我们需要跟进SpringApplication的run方法:
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
// 准备上下文
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
// 刷新上下文
很明显,初始化器完成初始化理应在准备上下文阶段的,跟进prepareContext方法:
org.springframework.boot.SpringApplication#prepareContext
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
context.setEnvironment(environment);
postProcessApplicationContext(context);
// 遍历context中的Initializers,调用它们的initialize方法
applyInitializers(context);
listeners.contextPrepared(context);
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// Add boot specific singleton beans
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
beanFactory.registerSingleton("springBootBanner", printedBanner);
}
if (beanFactory instanceof DefaultListableBeanFactory) {
((DefaultListableBeanFactory) beanFactory)
.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
// Load the sources
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
load(context, sources.toArray(new Object[0]));
listeners.contextLoaded(context);
}
- 我们之前构造出的一批初始化器就在context中。
跟进applyInitializers方法,此处【坐标2】:
org.springframework.boot.SpringApplication#applyInitializers
protected void applyInitializers(ConfigurableApplicationContext context) {
// getInitializers()就是获取前面的initializers集合
for (ApplicationContextInitializer initializer : getInitializers()) {
Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(),
ApplicationContextInitializer.class);
Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
// 这里就会调用实验代码覆写的initialize方法
initializer.initialize(context);
}
}
要是不信还可以打断点看看调试结果:
SecondInitializer的调用跟FirstInitializer大致相同,上面也说了SecondInitializer是如何被添加到initializers集合的。
好了,现在FirstInitializer、SecondInitializer的回调流程都明白了,且两者回调顺序也与设置的@Order一致,那么ThirdInitializer呢?为什么明明设置了@Order(3),但它却是第一个调用的呢?想要知道原因,需要更进一步的跟进源码。
ThirdInitializer解谜
从提供实验代码中不难猜测,ThirdInitializer调用的顺序不一样,大概率是由于配置加载的顺序不一样,而事实也确实如此,下面就来看看ThirdInitializer到底实在哪里被实例化的,为什么顺序不对,又是哪里调用了它的initialize方法。
视角重新回到上面遇到过的applyInitializers方法:
org.springframework.boot.SpringApplication#applyInitializers
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.");
initializer.initialize(context);
}
}
经过多次调试,ThirdInitializer的调用是在applyInitializers执行时,且遍历到FirstInitializer之前出现的,所以重点应该观察FirstInitializer之前的Initializer到底做了什么。
下面先来看看getInitializers()返回的集合:
- 此时不难推测,ThirdInitializer的相关操作肯定就是在前两个Initializer中的某一个完成的。
经过调试,果不其然,就是在DelegatingApplicationContextInitializer的initialize完成ThirdInitializer的相关操作的,下面跟进DelegatingApplicationContextInitializer#initialize:
org.springframework.boot.context.config.DelegatingApplicationContextInitializer#initialize
@Override
public void initialize(ConfigurableApplicationContext context) {
ConfigurableEnvironment environment = context.getEnvironment();
List<Class<?>> initializerClasses = getInitializerClasses(environment);
if (!initializerClasses.isEmpty()) {
applyInitializerClasses(context, initializerClasses);
}
}
先跟进getInitializerClasses方法:
org.springframework.boot.context.config.DelegatingApplicationContextInitializer#getInitializerClasses
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;
}
- 这一步完成了ThirdInitializer对象的构造并返回。
PROPERTY_NAME属性:
private static final String PROPERTY_NAME = "context.initializer.classes";
- 就是application.properties设置的值。
完成了ThirdInitializer对象的构造后,将对象返回到上面的initialize方法,我们继续往下跟,跟进applyInitializerClasses方法:
org.springframework.boot.context.config.DelegatingApplicationContextInitializer#applyInitializerClasses
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);
}
- 注意这里的initializers是新定义的,也会排序,但这里的initializers不是上文说到的SpringApplication的initializers。
- 这也说明:ThirdInitializer的@Order跟FirstInitializer、SecondInitializer不在同一层级上。
- 也就是说ThirdInitializer与(FirstInitializer、SecondInitializer)的执行顺序并没有直接关系,只是因为DelegatingApplicationContextInitializer的优先级比FirstInitializer、SecondInitializer高,所以ThirdInitializer的调用就在前面了。
继续跟进applyInitializers方法:
org.springframework.boot.context.config.DelegatingApplicationContextInitializer#applyInitializers
private void applyInitializers(ConfigurableApplicationContext context,
List<ApplicationContextInitializer<?>> initializers) {
initializers.sort(new AnnotationAwareOrderComparator());
for (ApplicationContextInitializer initializer : initializers) {
initializer.initialize(context);
}
}
- 同样是遍历+回调,这里的initialize就是回调到initialize方法。
- 当initializer是ThirdInitializer实例时,就会回调到它的initialize方法,即实验我们自定义的实验代码。
现在,从读取配置,构造Initializer、回调Initializer的initialize方法,我们都过一遍了,到这里你可能还有一个问题。
- Q:包括DelegatingApplicationContextInitializer在内,其余Initializer是在哪里声明了要初始化的呢?
- A:在介绍spring.factories的时候就说过了,SpringBoot原本就附带了一些配置文件,其中一个spring.factories就包含了DelegatingApplicationContextInitializer,不信看看下面的截图:
本节内容的核心大概就是之前springboot启动流程图的这一部分:
小结
- ApplicationContextInitializer的使用方式:实现ApplicationContextInitializer接口+覆写initialize方法。
- 在initialize方法中我们可以自定义一些业务逻辑,比如:初始化一些上下文属性。
- 添加Initializer有三种方式:
- application.properties中配置(优先级高于后面两种)。
- spring.factories中配置。
- SpringApplication#addInitializers
- 要注意ThirdInitializer这种配置方式会使@Order"失效"的问题。