文章首发于公众号,欢迎订阅
今天,我们来讨论一下 Spring Boot 初始化器的执行过程。
Spring Boot 有三种方式定义初始化器,下面逐一分析。
1、定义在 spring.factories
文件中,被 SpringFactoriesLoader
发现注册(工厂加载机制)
首先我们自定义一个类实现 ApplicationContextInitializer
。
public class DemoInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
ConfigurableEnvironment environment = applicationContext.getEnvironment();
Map<String, Object> map = new HashMap<>();
map.put("demo", 1);
MapPropertySource mapPropertySource = new MapPropertySource("demoInitializer", map);
environment.getPropertySources().addLast(mapPropertySource);
System.out.println("run demoInitializer");
}
}
然后在 resource 目录下面新建 /META-INF/spring.factories
文件,并写入如下一行配置。
org.springframework.context.ApplicationContextInitializer=com.example.initializer.DemoInitializer # 类路径
跟着 debug 大哥走
第一步,让我们从主函数进入,一窥究竟
第二步
第三步
第四步,进入 SpringApplication
的构造方法
第五步,进入重载构造方法
从 setInitializers
和 setListeners
,我们猜测,Listener 会不会也和 Initializer 一样,也是采用spring.factories
的方式来注册的。
第六步,进入 getSpringFactoriesInstances
方法
第七步,进入重载方法
我们在最后打个断点看看,卧槽,发现第一次到这里,初始化器已经成功获取了,有点意思!
第八步,进入查看 loadFactoryNames
方法
loadFactoryNames
方法调用了 loadSpringFactories
方法来获取配置信息。
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
// 1.查找缓存
MultiValueMap<String, String> result = cache.get(classLoader);
// 2.缓存存在,直接返回
if (result != null) {
return result;
}
try {
// 3.缓存不存在,读取指定资源文件
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);
// 4.构造Properties对象
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
// 5.获取key对应的value
String factoryClassName = ((String) entry.getKey()).trim();
for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
// 6.逗号分割value
result.add(factoryClassName, factoryName.trim());
}
}
}
// 7.保存结果到缓存
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
从上我们发现一个常量 FACTORIES_RESOURCE_LOCATION
,而它的值就是 META-INF/spring.factories
。
整个
loadSpringFactories
过程如下:
- 查找缓存,缓存存在,直接返回,否则继续
- 缓存不存在,读取指定资源文件
- 构造 Properties 对象
- 获取 key 对应的 value
- 逗号分割 value
- 保存结果到缓存
实际上,我们下面要谈到的 DelegatingApplicationContextInitializer
,也是由 Spring Boot 内置的 spring.factories
文件加载的。
第九步,代码又回到 getSpringFactoriesInstances
方法,此时调用 createSpringFactoriesInstances
方法,利用反射依次实例化结果对象
第十步,又回到 getSpringFactoriesInstances
方法,此时对结果对象排序
就是在初始化器上标注的 @Order
注解中的数字,0 表示优先级最高。
第十一步,整个 getSpringFactoriesInstances
方法执行完,此时这些初始化器全部初始化成功,且被 Spring 管理
实际上,这是一种 Service Provider Interface(服务发现机制),它通过在 ClassPath 路径下的 META-INF 文件夹查找文件,自动加载文件里所定义的类。比如在 Dubbo、JDBC、Tomcat 中都使用到了 SPI 机制。
2、SpringApplication
初始化完成后手动添加
只需要如下三行代码即可。
可以看到,实际上和方式一最终调用的 setInitializers
方法是一样的,这个相当简单,直接添加。
3、定义成环境变量,被 DelegatingApplicationContextInitializer
所发现注册(默认优先级最高)
这种方式和第一种的相似,只不过这里不用 spring.factories
这种形式,而是直接在配置文件中加入下面一行。
context.initializer.classes=com.example.initializer.DemoInitializer
首先进入 DelegatingApplicationContextInitializer
,查看如下方法
查看 getInitializerClasses
方法
注意到常量 PROPERTY_NAME
这也是为什么我们可以在配置文件中写入上述名称,不过写的时候是没有任何提示的。从配置文件中获取到对应的初始化类信息,然后执行初始化方法。同样配置文件中的类名以逗号分割,来获得每个所需的系统初始化器的全限定类名。
Q:初始化器是何时被调用的,也就是何时执行 initialize
方法的?
实际上在上面 debug 的过程中,我带着大家一直在看 SpringApplication
的构造初始化方法,然而最后还有一步 run
方法没有分析。
run 方法代码过多, 我截取一部分
我们进入 prepareContext
方法查看
没错,应该就是这个了,再点进去看一下
这不,首先获取了实现 ApplicationContextInitializer
接口的实现类,然后分别调用实现类各自的 initialize
方法。
Q:为什么 DelegatingApplicationContextInitializer
加载的初始化器是优先于其他方式执行呢?
DelegatingApplicationContextInitializer
的 order 为 0,因此优先级最高,会被最先加载。
我们可以从它的类定义中看到
所以我们在上面 debug 时才会看到DelegatingApplicationContextInitializer
是排在第一的。
如果你给自定义的初始化器的 order 赋值为 0,那么自定义初始化器就是第一个了。
好了,下面该轮到你们表演了!
思考题
- 谈谈你对
SpringFactoriesLoader
的理解,SpringFactoriesLoader
是如何加载工厂类的? - 系统初始化器的作用(可以结合系统自带的初始化器的作用)。
- 系统初始化器何时调用?
- 如何实现自定义系统初始化器?
- 自定义系统初始化器有没有什么注意事项?
答案我们下期给出!
我创建了一个免费的知识星球,用于分享知识日记,欢迎加入!