转载

Spring探索02 – SpringBoot启动分析之getSpringFactoriesInstances

最近计划整理下SpringBoot的启动过程以及API请求返回过程,以便支持后续的部分内容,也方便实现一周一文的计划。

先做SpringBoot启动流程分析。

初见

在SpringBoot启动代码的开头部分可以看到如下的内容:

setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

比较显眼的是 getSpringFactoriesInstances 方法——这个方法出现了两次,而且以后还会继续出现。

从代码上看来,这个方法的作用应该就是获取指定类型的实例集合。

具体如何,还得继续走走看。从这里一步步走下去,可以看到一个重载的方法,这个方法的内容比较实在:

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
	ClassLoader classLoader = getClassLoader();
	// Use names and ensure unique to protect against duplicates
	Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
	List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
	AnnotationAwareOrderComparator.sort(instances);
	return instances;
}

关键是方法体中的三四两行。第三行应该是获取了名称集合,第四行则应是获取了名称对应的实例集合。

问名

首先看看 SpringFactoriesLoader . loadFactoryNames 方法做了哪些事情。这个方法的核心内容在于它调用的 loadSpringFactories 方法。 loadSpringFactories 的方法体稍稍长了一些,沾出来显得太啰嗦,所以仅在下面介绍下这个方法具体做了那些事情。

这个类的作用是根据类加载器获取类名称集合(map结构,接口->实现类集合),具体流程如下:

  1. 首先尝试从内存缓存中获取,如获取到就立即返回,没有则继续下面内容;
  2. 从类加载路径(的jar文件)中获取全部的 spring.factories 文件路径;
  3. 循环遍历读取这些文件中的键值对(K->List[V]);
  4. 将读取内容放入内存缓存,下次再调用这个方法时会优先从缓存中获取;
  5. 返回读取到的全部键值对集合(Map[K,List[V]]结构)。

简单做些解释:

spring.factories文件本质上是一个.properties文件,也就是说,它的内容是键值对集合,下面是一段示例:

# PropertySource Loaders
org.springframework.boot.env.PropertySourceLoader=/
org.springframework.boot.env.PropertiesPropertySourceLoader,/
org.springframework.boot.env.YamlPropertySourceLoader
 
# Run Listeners
org.springframework.boot.SpringApplicationRunListener=/
org.springframework.boot.context.event.EventPublishingRunListener
 
# Error Reporters
org.springframework.boot.SpringBootExceptionReporter=/
org.springframework.boot.diagnostics.FailureAnalyzers

这个片段中有三个键值对。根据后面的代码可知 spring.factories 文件中每个键值对的key是一个接口的名称,value则是实现类名称的集合(以“,”分隔)。

spring.factories文件中的内容会被读取到一个(K->List[V])结构的Map中。最终全部这些内容又会以类加载器实例为key保存在内存缓存中。

然后, SpringFactoriesLoader . loadFactoryNames 方法就可以根据接口的名称获取到接口的实现类的名称集合了。

得意

在得到类名称集合后就是根据类名构建类对应的实例了。这是 createSpringFactoriesInstances 方法做的事情。这个方法不长,可以粘出来看看:

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<?> 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;
}

方法的内容很清晰也很简单:

  1. 根据方法名获取 Class 实例;
  2. 通过 Class 实例获取构造器;
  3. 使用构造器构建类实例。

很普通的一个反射过程。期间有一个校验是判断 Class 是不是指定接口的子类;还有就是在 BeanUtils . instantiateClass 中创建实例的时候修改了构造器的可见性范围,并提供了对Kotlin的支持。

这块儿内容大体上就这样了。至于这里获取到了哪些 ApplicationContextInitializer ApplicationListener 的实例,以及它们的作用,在后续的部分会陆续提到。

End!

原文  https://www.zhyea.com/2020/01/03/spring-explore-02-springboot-boot-flow-analyze-getspringfactoriesinstances.html
正文到此结束
Loading...