SpringBoot 启动之如何加载并处理 META-INF/spring.factories 文件

一、前言

看Spring Boot源码的时候,发现在SpringApplication初始化阶段会加载Spring应用上下文初始化器(ApplicationContextInitializer)、加载Spring应用事件监听器(ApplicationListener);而 ApplicationContextInitializer 和 ApplicationListener 内建的实现类预置在spring-boot jar包的 META-INF/spring.factories 文件中;
在这里插入图片描述
此外,在spring-boot-autoconfigure jar包的META-INF/spring.factories文件中也有一部分:
在这里插入图片描述
所以,在Spring Boot中一共内建了11个ApplicationListener、7个ApplicationContextInitializer。

二、正文

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

整体的处理流程为:
在这里插入图片描述

1、找到type的所有实现类

使用Spring工厂加载机制方法SpringFactoriesLoader.loadFactoryName(Class,ClassLoader)来做这个操作;
在这里插入图片描述
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>中。

注意,在获取到type类所有实现类的类名后会用Set集合做一个去重。
在这里插入图片描述

2)classLoader.getResources(FACTORIES_RESOURCE_LOCATION)

此处的classLoader为AppClassLoader;比较有意思的是getResources(FACTORIES_RESOURCE_LOCATION)方法使用F7进不去,要进去到AppClassLoader里打断点。
在这里插入图片描述

AppClassLoaderLauncher的静态内部类,其类图如下:
在这里插入图片描述
即,AppClassLoader间接继承自ClassLoader,而getResources(String name)方法在其类结构中只出现在ClassLoader中,所以要去ClassLoader中打断点;
在这里插入图片描述
加载Resource资源时也会用到父类加载器。递归由AppClassLoader 的父加载器 ExtClassLoader 负责加载Resource资源;最终体现为:Enumeration<URL>[]数组的0下标所表示其父类、祖父类加载器加载到的Resources资源,而1下标处表示自己加载到Resources资源,这和双亲委派机制的不一样的点。

AppClassLoader 的父类、祖父类加载器并没有加载到任何资源(因为META-INF/Spring.factories文件也只存在于AppClassLoader的扫描的目录下)。
在这里插入图片描述
最后看一下AppClassLoader是怎么找到所有的META-INF/spring.factories文件的?

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

在这里插入图片描述
在URLClassLoader内部会调用其组合的URLClassPath类的findResources(String, boolean)方法去做一个真正的资源扫描操作。
在这里插入图片描述
最终效果如下,但是不建议追(太深了,并且很不好debug)。
在这里插入图片描述
但是有一点我们可以记住:hasMoreElements() 和 nextElement()方法均出自sun.misc包下的CompoundEnumeration类。

在这里插入图片描述

3)loadSpringFactories(ClassLoader)返回结果

在这里插入图片描述
接着通过Map的getOrDefault()方法获取到result中key为ApplicationContextInitializer的value。
在这里插入图片描述
最后回到SpringApplication#getSpringFactoriesInstances()方法中,使用Set集合来接返回值,以达到一个去重的效果。
在这里插入图片描述

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、做排序

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

AnnotationAwareOrderComparator.sort(instances);

AnnotationAwareOrderComparator类继承自OrderComparator,排序规则体现在OrderComparator类中的compare()方法,规则具体如下:

(1) 优先按对象是否实现PriorityOrdered接口排序。

(2) 其次,通过OrderComparator#getOrder()方法获取排列的序列值。getOrder()方法中会通过findOrder()方法查找序列值,而AnnotationAwareOrderComparator重写了findOrder()方法,所以会调用 AnnotationAwareOrderComparator#findOrder()方法。

(3) 在AnnotationAwareOrderComparator#findOrder()方法中,会先调用其父类OrderComparator#findOrder()方法判断对象是否实现Ordered接口,若实现该接口则返回其值。

(4) 通过findOrderFromAnnotation()方法判断对象是否被 @Order 注解标注,若是,则返回其值。

(5) 判断对象是否被 @Priority 注解标注,若是,则返回其值。

(6) 最后,在OrderComparator#getOrder()方法中,如果findOrder()返回了具体的Integer值,则返回,否则返回 Integer.MAX_VALUE。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值