0
点赞
收藏
分享

微信扫一扫

《SpringBoot启动流程一》:万字debug梳理SpringBoot如何加载并处理META-INF/spring.factories文件中的信息


一、前言

看Spring Boot源码的时候,发现在SpringApplication初始化阶段会加载Spring应用上下文初始化器(ApplicationContextInitializer)、加载Spring应用事件监听器(ApplicationListener);而ApplicationContextInitializer 和 ApplicationListener内建的实现类预置在​​spring-boot​​ jar包的META-INF/spring.factories文件中;

《SpringBoot启动流程一》:万字debug梳理SpringBoot如何加载并处理META-INF/spring.factories文件中的信息_jar包


此外,在​​spring-boot-autoconfigure​​ jar包的META-INF/spring.factories文件中也有一部分:

《SpringBoot启动流程一》:万字debug梳理SpringBoot如何加载并处理META-INF/spring.factories文件中的信息_jar包_02


所以,在Spring Boot中一共内建了11个ApplicationListener、7个ApplicationContextInitializer。

那么SpringBoot是怎么将其加载到Spring容器中的呢?怎么加载到SpringApplication中的呢?我们就此展开研究。

二、正文

入口

无论是在SpringApplication初始化阶段时加载Spring事件监听器ApplicationListener、Spring应用上下文初始化器ApplicationContextInitializer,还是在SpringApplication准备阶段时加载Spring运行时监听器SpringApplicationRunListener、异常报告器SpringBootExceptionReporter,都要从​​SpringApplication#getSpringFactoriesInstances()​​​重载方法开始,并且进入到​​getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args)​​;

《SpringBoot启动流程一》:万字debug梳理SpringBoot如何加载并处理META-INF/spring.factories文件中的信息_加载_03


以Spring应用上下文初始化器为例,此处的​​type​​​为​​ApplicationContextInitializer​​;整体的处理流程为:

《SpringBoot启动流程一》:万字debug梳理SpringBoot如何加载并处理META-INF/spring.factories文件中的信息_jar包_04

下面我们分开来看;

1、找到type的所有实现类

使用Spring工厂加载机制方法​​SpringFactoriesLoader.loadFactoryName(Class,ClassLoader)​​来做这个操作;

《SpringBoot启动流程一》:万字debug梳理SpringBoot如何加载并处理META-INF/spring.factories文件中的信息_spring boot_05


​SpringFactoriesLoader.loadFactoryName(Class,ClassLoader)​​中首先会根据类加载器加载出所有spring.factories中的所有内容。

1)loadSpringFactories(ClassLoader)

​loadSpringFactories(ClassLoader)​​会解析所有加载的jar包中 META-INF/spring.factories配置文件的配置内容,并组装为Map<String, List>数据结构,方法返回。具体流程如下:

  1. 首先,去缓冲中查询是否有入参classLoader对应的配置信息(仅第一次加载spring.factories文件时不走缓存),如果存在,则表明服务之前解析过配置文件 并 方法返回。如果不存在,则进行解析操作。
  2. 其次,获得所有依赖jar包中,具有META-INF/spring.factories配置文件的jar文件URI,并依次进行遍历。
  3. 接着,将​​spring.factories​​​配置的内容转化成​​properties​​​实例;遍历properties实例,将key和value维护到​​Map<String, List<String>>​​ result数据结构中,如果多个spring.factories中的key相同,则value取合集。
  4. 最后,将result维护到缓冲cache中——key=ClassLoader value=result;并将result作为返回值返回。

<1> 缓存cache数据结构:

static final Map<ClassLoader, Map<String, List<String>>> cache = new ConcurrentReferenceHashMap<>();

<2> 方法主体:

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
// 1、去缓存中,查询是否有入参classLoader对应的配置信息,
// 如果存在,则表明服务之前解析过配置文件。如果不存在,则进行解析操作
MultiValueMap<String, String> result = cache.get(classLoader);
// 缓存中存在则直接返回
if (result != null) {
return result;
}

try {
// 2、获得所有依赖jar包中,具有META-INF/spring.factories配置文件的jar文件URI
// todo 问自己一个问题,它是怎么找到的?
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
// 遍历所有的URI
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
// 通过url获得资源resource
UrlResource resource = new UrlResource(url);
// 3、将spring.factories配置的内容转化成properties实例
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
// 4、遍历properties实例,将key和value维护到Map<String, List<String>> result数据结构中
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryTypeName = ((String) entry.getKey()).trim();
// StringUtils.commaDelimitedListToStringArray只是单纯的将字符串转为String[]数组
for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
result.add(factoryTypeName, factoryImplementationName.trim());
}
}
}
// 4、将result维护到缓冲cache中——key=ClassLoader value=result
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}

在遍历properties实例,将key和value维护到Map<String, List> result数据结构的过程中,可以发现一个问题:如果多个spring.factories文件中针对同一个key有相同的value值,那岂不就是重复添加了。

假设,我依赖的某一个jar包的META-INF/factories中和​​spring-boot​​​ jar包的META-INF/factories中都有 ​​org.springframework.context.ApplicationContextInitializer=\ org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\​​​。则​​ConfigurationWarningsApplicationContextInitializer​​​会被添加两次到​​ApplicationContextInitializer​​​对应的​​List<String>​​中。

spring不会有这种bug吧?显然是不可能的,往上追,追到SpringApplication类中,在获取到type类所有实现类的类名时会用Set集合做一个去重。

《SpringBoot启动流程一》:万字debug梳理SpringBoot如何加载并处理META-INF/spring.factories文件中的信息_spring_06


思考一下为什么不能在最底层就做去重呢?而需要每个调用方都自己去重!

2)classLoader.getResources(FACTORIES_RESOURCE_LOCATION)

我很好奇,classLoader.getResources(FACTORIES_RESOURCE_LOCATION)它是如何找到所有META-INF/spring.factories文件的?所以这里特意写一小节。

此处的classLoader为​​AppClassLoader​​​;比较有意思的是​​getResources(FACTORIES_RESOURCE_LOCATION)​​方法使用F7进不去,要进去到AppClassLoader里打断点。

《SpringBoot启动流程一》:万字debug梳理SpringBoot如何加载并处理META-INF/spring.factories文件中的信息_java_07

​AppClassLoader​​​是​​Launcher​​的静态内部类,其类图如下:

《SpringBoot启动流程一》:万字debug梳理SpringBoot如何加载并处理META-INF/spring.factories文件中的信息_jar包_08


即,AppClassLoader间接继承自ClassLoader,而​​getResources(String name)​​​方法在其类结构中只出现在​​ClassLoader​​中,所以要去ClassLoader中打断点;

《SpringBoot启动流程一》:万字debug梳理SpringBoot如何加载并处理META-INF/spring.factories文件中的信息_java_09


加载Resource资源时也会用到父类加载器。递归由​​AppClassLoader​​​ 的父加载器 ​​ExtClassLoader​​​ 负责加载Resource资源;最终体现为:​​Enumeration<URL>[]​​​数组的0下标所表示其父类、祖父类加载器加载到的Resources资源,而1下标处表示自己加载到Resources资源,这也和双亲委派机制的不一样的点。而​​AppClassLoader​​ 的父类、祖父类加载器并没有加载到任何资源(因为META-INF/Spring.factories文件也只存在于AppClassLoader的扫描的目录下)。

《SpringBoot启动流程一》:万字debug梳理SpringBoot如何加载并处理META-INF/spring.factories文件中的信息_jar包_10


最后看一下AppClassLoader是怎么找到所有的META-INF/spring.factories文件的?

1> 因为​​ClassLoader#findResources(String)​​​是一个抽象方法,具体逻辑由子类实现,结合AppClassLoader的类图,定位到​​URLClassLoader#findResources(String)​​;

《SpringBoot启动流程一》:万字debug梳理SpringBoot如何加载并处理META-INF/spring.factories文件中的信息_jar包_11


在URLClassLoader内部会调用其组合的​​URLClassPath​​类的findResources(String, boolean)方法去做一个真正的资源扫描操作。

《SpringBoot启动流程一》:万字debug梳理SpringBoot如何加载并处理META-INF/spring.factories文件中的信息_spring boot_12


最终效果如下,但是不建议追(太深了,并且很不好debug)。

《SpringBoot启动流程一》:万字debug梳理SpringBoot如何加载并处理META-INF/spring.factories文件中的信息_jar包_13


但是有一点我们可以记住:hasMoreElements() 和 nextElement()方法均出自sun.misc包下的​​CompoundEnumeration​​类。有兴趣的建议参考博主打断点的思路继续深追如下代码段:

《SpringBoot启动流程一》:万字debug梳理SpringBoot如何加载并处理META-INF/spring.factories文件中的信息_jar包_14

3)loadSpringFactories(ClassLoader)返回结果

《SpringBoot启动流程一》:万字debug梳理SpringBoot如何加载并处理META-INF/spring.factories文件中的信息_spring_15


接着通过Map的getOrDefault()方法获取到result中key为​​ApplicationContextInitializer​​的value。

《SpringBoot启动流程一》:万字debug梳理SpringBoot如何加载并处理META-INF/spring.factories文件中的信息_加载_16


最后回到​​SpringApplication#getSpringFactoriesInstances()​​方法中,使用Set集合来接返回值,以达到一个去重的效果。

《SpringBoot启动流程一》:万字debug梳理SpringBoot如何加载并处理META-INF/spring.factories文件中的信息_spring_17

2、实例化type的所有实现类

紧接着上面进入到createSpringFactoriesInstances()方法根据类的全路径名做实例化操作。

List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);

遍历所有的全路径类名,使用AppClassLoader将相应Class文件从磁盘装载到内存中,然后利用反射获取Class类的无参构造函数、实例化对象。

注意:要实例化的类必须要有无参构造函数。

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文件到内存
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;
}

3、做Order排序

将type类对应的所有实现类实例化完毕之后,要对他们做一个根据Order的排序。

AnnotationAwareOrderComparator.sort(instances);

sort()方法中直接使用List集合的sort()方法,但需要自定义​​Comparator​​​为当前类实例​​AnnotationAwareOrderComparator​​。

《SpringBoot启动流程一》:万字debug梳理SpringBoot如何加载并处理META-INF/spring.factories文件中的信息_jar包_18


再看​​AnnotationAwareOrderComparator​​的类结构:

《SpringBoot启动流程一》:万字debug梳理SpringBoot如何加载并处理META-INF/spring.factories文件中的信息_java_19


AnnotationAwareOrderComparator继承自​​OrderComparator​​​,自定义Comparator需要实现Comparator的抽象方法​​compare(T o1, T o2)​​。AnnotationAwareOrderComparator自身没有compare()方法,所以看其父类OrderComparator中的compare方法;

《SpringBoot启动流程一》:万字debug梳理SpringBoot如何加载并处理META-INF/spring.factories文件中的信息_spring_20


因为​​OrderComparator#doCompare(Object o1, Object o2, OrderSourceProvider sourceProvider)​​​方法中的入参sourceProvider为null,所以进入到getOrder()方法时,后续直接调用子类的​​findOrder(Object obj)​​方法去查找相应类的顺序值。

《SpringBoot启动流程一》:万字debug梳理SpringBoot如何加载并处理META-INF/spring.factories文件中的信息_java_21


AnnotationAwareOrderComparator#findOrder()方法中先调用父类​​OrderComparator#findOrder()​​方法,如果找到顺序值直接返回,否者从类的@Order注解中取到顺序值。

1> 先看OrderComparator#findOrder()方法:

  • 该方法判断obj有没有实现Ordered接口,实现Ordered接口之后,有没有重写其getOrder()方法,如果重写了,则直接从getOrder()中获取到序列值。

@Nullable
protected Integer findOrder(Object obj) {
return (obj instanceof Ordered ? ((Ordered) obj).getOrder() : null);
}

以ContextIdApplicationContextInitializer为例,其getOrder()方法返回的序列值为​​2147483637​​;

《SpringBoot启动流程一》:万字debug梳理SpringBoot如何加载并处理META-INF/spring.factories文件中的信息_spring_22

2> 再看AnnotationAwareOrderComparator#findOrder()方法:

  • 如果通过​​OrderComparator#findOrder()​​​获取不到序列值,则通过​​findOrderFromAnnotation()​​方法从@Order注解中获取序列值。

@Override
@Nullable
protected Integer findOrder(Object obj) {
Integer order = super.findOrder(obj);
if (order != null) {
return order;
}
return findOrderFromAnnotation(obj);
}

以ConfigurationWarningsApplicationContextInitializer为例:

《SpringBoot启动流程一》:万字debug梳理SpringBoot如何加载并处理META-INF/spring.factories文件中的信息_加载_23

​findOrderFromAnnotation()​​​方法中通过​​OrderUtils.getOrderFromAnnotations(element, annotations);​​获取@Order注解中的值:

《SpringBoot启动流程一》:万字debug梳理SpringBoot如何加载并处理META-INF/spring.factories文件中的信息_加载_24


​OrderUtils.getOrderFromAnnotations(element, annotations);​​方法返回之后,如果order为null,则直接返回null。

《SpringBoot启动流程一》:万字debug梳理SpringBoot如何加载并处理META-INF/spring.factories文件中的信息_加载_25


最后返回到​​OrderComparator#getOrder()​​​方法,如果order为null,则将Order设置为​​Integer.MAX_VALUE​​;

《SpringBoot启动流程一》:万字debug梳理SpringBoot如何加载并处理META-INF/spring.factories文件中的信息_jar包_26


如果两个对象的Order序列值一样,则按原本在集合中的顺序先后排列。

1)总述

利用List集合自身的排序,通过传入自定义的Comparator 实现排序规则。规则具体如下:

  • AnnotationAwareOrderComparator类继承自OrderComparator,排序规则体现在OrderComparator类中的compare()方法;
  • 每个排序对象都会通过​​OrderComparator#getOrder()​​方法获取排列的序列值。
  • getOrder()方法中首先通过​​findOrder()​​方法查找序列值,而AnnotationAwareOrderComparator重写了findOrder()方法。所以调用findOrder()方法会先进入到AnnotationAwareOrderComparator#findOrder()方法。
  • 在AnnotationAwareOrderComparator#findOrder()方法中,会先调用其父类OrderComparator#findOrder()方法判断对象是否实现​​Ordered​​接口 并 重写了getOrder()方法,有则返回getOrder()的值。
  • 否则通过​​findOrderFromAnnotation()​​方法从对象的@Order注解中获取具体的值,如果对象没有被@Order注解标注,则返回null。
  • 最后到OrderComparator#getOrder()层面,如果findOrder()返回了具体的Integer值,则返回,否者返回Integer.MAX_VALUE。

下篇接着聊SpringBoot启动流程之SpringApplication准备阶段。


举报

相关推荐

0 条评论