Spring Boot 回顾(四):深入理解SpringFactoriesLoader

1,925 阅读3分钟

这是我参与8月更文挑战的第4天,活动详情查看:8月更文挑战

前言

上一篇我们讲到Spring Boot自动装配的原理,而且通过阅读源码,我们看到其实自动装配的奥义其实就是SpringFactoriesLoader,今天我们再来揭秘下SpringFactoriesLoader的神秘面纱。

介绍

SpringFactoriesLoader工厂的加载机制类似java提供的SPI机制一样,是Spring提供的一种加载方式。只需要在classpath路径下新建一个文件META-INF/spring.factories,并在里面按照Properties格式填写好接口和实现类即可通过SpringFactoriesLoader来实例化相应的Bean。其中key可以是接口、注解、或者抽象类的全名。value为相应的实现类,当存在多个实现类时,用“,”进行分割。其实关于spring.factories的实现也是Spring Boot中约定优于配置的体现,可以说,整个Spring Boot是遵循约定优于配置这个理念产生的,因此它才能如此的快而且简单。

原理

先放一张SpringFactoriesLoader工作的流程图

graph LR
A[Start] -->B{查找缓存}--> |不存在| 1[读取指定路径下的资源文件]-->C[构建Properties对象]
-->D[获取指定key对应的value值]-->E[逗号分割value值]-->F[保存结果到缓存]-->2[依次实例化对象]-->3[对结果进行排序]-->4[返回结果]-->5[End]
B{查找缓存}--> |存在| 2[依次实例化对象]

接下来我们进入重点,打开SpringFactoriesLoader的源码,里面比较简单,有2个成员变量和4个方法,其中方法分别是loadFactoriesloadFactoryNamesloadSpringFactoriesinstantiateFactory。我们先看下成员变量


public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
private static final Map<ClassLoader, MultiValueMap<String, String>> cache = new ConcurrentReferenceHashMap<>();

其中第一个是寻找工厂的位置,工厂可以存放在多个jar文件中,第二个则是自定义用于存储工厂的缓存。然后我们了解下其中的方法,首先是loadFactories这个方法。

public static <T> List<T> loadFactories(Class<T> factoryType, @Nullable ClassLoader classLoader) {
  Assert.notNull(factoryType, "'factoryType' must not be null");
  // 如果未指定类加载器,则使用默认的
  ClassLoader classLoaderToUse = classLoader;
  if (classLoaderToUse == null) {
    classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
  }
  // 获取指定工厂名称列表
  List<String> factoryImplementationNames = loadFactoryNames(factoryType, classLoaderToUse);
  // 如果记录器Trace跟踪激活的话,将工厂名称列表输出
  if (logger.isTraceEnabled()) {
    logger.trace("Loaded [" + factoryType.getName() + "] names: " + factoryImplementationNames);
  }
  // 创建结果集
  List<T> result = new ArrayList<>(factoryImplementationNames.size());
  for (String factoryImplementationName : factoryImplementationNames) {
    // 实例化工厂类,并添加到结果集中
    result.add(instantiateFactory(factoryImplementationName, factoryType, classLoaderToUse));
  }
  // 对结果集列表进行排序
  AnnotationAwareOrderComparator.sort(result);
  return result;
}

可以看到大致的工作流程如下:

  1. 通过classLoader去加载工厂获取其对应类名称,如果未指定类加载器,则使用默认的;
  2. 通过instantiateFactory方法实例化工厂类,并添加到结果集中;
  3. 通过AnnotationAwareOrderComparator#sort方法对工厂进行排序; 接着我们看下loadFactoryNames,这个比较简单,主要逻辑都在loadSpringFactories,源码就不放上来了,其逻辑大致是读取 classpath上 所有的 jar 包中的所有 META-INF/spring.factories属 性文件,找出其中定义的匹配类型 factoryClass 的工厂类,然后并返回这些工厂类的名字列表,注意是包含包名的全限定名。最后是instantiateFactory方法,它的主要作用就是实例化Bean对象

总结

以上我们通过阅读了SpringFactoriesLoader的相关代码加深了对其理解,总结起来就是SpringFactoriesLoader是用于Spring框架内部的通用工厂加载机制。SpringFactoriesLoader通过loadFactories方法来加载并实例化来自FACTORIES_RESOUCE_LOCATION路径中的文件给定的工厂类型,而这些文件可能包含在类路径的jar包中。这些文件通常都命名为spring.factories,并且都是以properties属性作为格式,文件中key表示的是接口或者抽象类的全限定名称,而值是以逗号分隔的实现类的名称列表。