今天,我们来讨论一下 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
是如何加载工厂类的? 答案我们下期给出!