今天,我们来讨论一下 Spring Boot 初始化器的执行过程。
Spring Boot 有三种方式定义初始化器,下面逐一分析。
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 方法
[图片上传失败...(image-e916cb-1587300696695)]
第七步,进入重载方法
我们在最后打个断点看看,卧槽,发现第一次到这里,初始化器已经成功获取了,有点意思!
第八步,进入查看 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 过程如下:
实际上,我们下面要谈到的 DelegatingApplicationContextInitializer ,也是由 Spring Boot 内置的 spring.factories 文件加载的。
第九步,代码又回到 getSpringFactoriesInstances 方法,此时调用 createSpringFactoriesInstances 方法,利用反射依次实例化结果对象
第十步,又回到 getSpringFactoriesInstances 方法,此时对结果对象排序
就是在初始化器上标注的 @Order 注解中的数字,0 表示优先级最高。
第十一步,整个 getSpringFactoriesInstances 方法执行完,此时这些初始化器全部初始化成功,且被 Spring 管理
实际上,这是一种 Service Provider Interface (服务发现机制),它通过在 ClassPath 路径下的 META-INF 文件夹查找文件,自动加载文件里所定义的类。比如在 Dubbo、JDBC、Tomcat 中都使用到了 SPI 机制。
SpringApplication 初始化完成后手动添加 只需要如下三行代码即可。
可以看到,实际上和方式一最终调用的 setInitializers 方法是一样的,这个相当简单,直接添加。
DelegatingApplicationContextInitializer 所发现注册(默认优先级最高) 这种方式和第一种的相似,只不过这里不用 spring.factories 这种形式,而是直接在配置文件中加入下面一行。
context.initializer.classes=com.example.initializer.DemoInitializer
首先进入 DelegatingApplicationContextInitializer ,查看如下方法
查看 getInitializerClasses 方法
注意到常量 PROPERTY_NAME
这也是为什么我们可以在配置文件中写入上述名称,不过写的时候是没有任何提示的。从配置文件中获取到对应的初始化类信息,然后执行初始化方法。同样配置文件中的类名以逗号分割,来获得每个所需的系统初始化器的全限定类名。
initialize 方法的? 实际上在上面 debug 的过程中,我带着大家一直在看 SpringApplication 的构造初始化方法,然而最后还有一步 run 方法没有分析。
run 方法代码过多, 我截取一部分
我们进入 prepareContext 方法查看
没错,应该就是这个了,再点进去看一下
这不,首先获取了实现 ApplicationContextInitializer 接口的实现类,然后分别调用实现类各自的 initialize 方法。
DelegatingApplicationContextInitializer 加载的初始化器是优先于其他方式执行呢? DelegatingApplicationContextInitializer 的 order 为 0,因此优先级最高,会被最先加载。
我们可以从它的类定义中看到
所以我们在上面 debug 时才会看到 DelegatingApplicationContextInitializer 是排在第一的。
如果你给自定义的初始化器的 order 赋值为 0,那么自定义初始化器就是第一个了。
好了,下面该轮到你们表演了!
SpringFactoriesLoader 的理解, SpringFactoriesLoader 是如何加载工厂类的? 答案我们下期给出!