SpringFactoriesLoader类和spring.factories文件

本文将介绍spring-core中的SpringFactoriesLoaderspring.factories

​1 引言

Spring Web MVC配置方式汇总(二)Spring Web MVC配置方式汇总(三)中我们介绍了反射、SPI和SCI机制。这些技术框架或名词看似毫不相关,其实本质上处理的是一样的问题,都是将程序或应用的部分控制逻辑暴露出去,便于配置和调整。

控制反转是IoC容器的标准特性,我们曾经在文章中也说过:Spring的IoC容器的设计思想便是将对象管理、对象与对象间的依赖关系从系统中抽离出来,交由外部控制。

但无论框架怎么改变,架构怎么设计,也不管对象管理、对象与对象间的依赖关系交由谁来负责,它总在那儿,无法抹去。变化的只是形式,但信息却不能丢失。

Spring框架提供了大量的注解来支持IoC容器,管理Bean对象的创建、使用和销毁。另外,Spring中也提供了一些工具类,这些工具类非常适合在上述场景下使用。

SpringFactoriesLoader就是其中的一个,这篇文章会对其机制进行介绍。

2 例子

我们先用一个例子解释SpringFactoriesLoader是如何使用的。

首先需要在pom.xml中增加必要的配置和依赖,主要是需要引入spring-core包。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>org.example</groupId>
    <artifactId>spring-core</artifactId>
    <version>1.0-SNAPSHOT</version>
    <properties>
        <java.version>1.8</java.version>
        <maven.compiler.source>${java.version}</maven.compiler.source>
        <maven.compiler.target>${java.version}</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>5.2.8.RELEASE</version>
        </dependency>
    </dependencies>
</project>

设计一个Application类,编写main方法并创建一个实现了Converter接口的名为NoOpConverter的内部类。

public class Application {
    public static void main(String[] args) {
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        List<Converter> converters =  SpringFactoriesLoader.loadFactories(Converter.class, classLoader);
        System.out.println(converters);
    }
    static class NoOpConverter implements Converter {
        public Object convert(Object o, Class aClass, Object o1) {
            return null;
        }
    }
}

resources目录下增加子文件夹META-INF,在META-INF中增加一个名为spring.factories的文件,文件内容为:

org.springframework.cglib.core.Converter=org.example.app.Application$NoOpConverter

运行程序,得到的结果如下图所示:

SpringFactoriesLoader运行结果示例

这样的运行结果和SPI以及SCI的运行结果非常相似。只不过在SPI和SCI中,定义文件需要放在META-INF/services目录下,文件名称需要定义为接口名称。在这里,定义文件是META-INF/spring.factories,文件内容为接口名称=实现类

3 源码解释

下图是SpringFactoriesLoader.loadFactories(...)方法的javadoc,其中指出了该方法的用途。该方法从META-INF/spring.factories文件中加载指定类型的对象的实现并返回给调用者。

SpringFactoriesLoader.loadFactories的javadoc

SpringFactoriesLoader.loadFactories(...)的代码比较简单,主要就是调用loadSpringFactories(...)方法拿到META-INF/spring.factories文件中定义的所有对象的清单列表,然后通过传入的对象类型进行过滤后返回。

loadSpringFactories(...)方法首先从cache中尝试获取数据,如果没有获取到则开始遍历classpath,拿到所有META-INF/spring.factories文件。读取这些文件的内容后,加载到cache中再返回。参考下面展示的代码段:

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
  MultiValueMap<String, String> result = cache.get(classLoader);
  if (result != null) return result;
  try {
    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);
      Properties properties = PropertiesLoaderUtils.loadProperties(resource);
      for (Map.Entry<?, ?> entry : properties.entrySet()) {
        String factoryTypeName = ((String) entry.getKey()).trim();
        for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
          result.add(factoryTypeName, factoryImplementationName.trim());
        }
      }
    }
    cache.put(classLoader, result);
    return result;
  } catch (IOException ex) {
  ...
  }
}

4 总结

本文主要介绍SpringFactoriesLoader的功能和实现机制,该类具有如下特性:

  • 读取所有META-INF/spring.factories文件的配置内容,它为使用了它的应用框架提供了一个稳定的扩展点;

  • 无需实例化,核心方法是静态方法,因此不需要通过容器来管理。也就是说,SpringFactoriesLoader提供的加载配置信息的功能可以在应用还未创建ApplicationContext之前使用;

SpringFactoriesLoader可以作为IoC容器(即各种不同类型的ApplicationContext)启动的入口。实际上很多框架也确实在spring.factories中定义了和其相关的各种接口实现类,以帮助整个框架完成初始化。

### spring.factories 配置未生效的原因及解决方案 #### 可能原因分析 1. **`spring.factories` 文件路径错误** `spring.factories` 文件必须放置在 `META-INF/spring.factories` 路径下,如果文件位置不正确,则不会被 Spring Boot 加载[^3]。 2. **加载器问题** 如果自定义的加载器未能正确加载 `spring.factories` 中定义的,可能导致配置失效。Spring Boot 通过加载器从指定路径中获取 `spring.factories` 文件并解析其内容[^2]。 3. **重复注解或冲突** 使用 `@SpringBootApplication` 注解时,默认会启用 `@EnableAutoConfiguration` 组件扫描功能 (`@ComponentScan`)。如果手动添加了相同的注解或者存在其他冲突逻辑(如多个 `@ComponentScan` 或者重复的自动配置),可能会导致某些配置项被覆盖或忽略[^1]。 4. **依赖版本不一致** 不同版本的 Spring Boot 对 `spring.factories` 的处理方式可能存在差异。如果项目中的依赖版本混杂,也可能引发此问题[^5]。 5. **配置未正确定义** 在 `spring.factories` 文件中声明的如果没有实现相应的接口(例如 `org.springframework.boot.autoconfigure.EnableAutoConfiguration` 接口),则该不会被识别为有效的自动配置[^3]。 --- #### 解决方案 ##### 方法一:确认文件路径格式 确保 `spring.factories` 文件位于 `src/main/resources/META-INF/` 下,并且遵循正确的键值对格式。例如: ```properties org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.example.MyCustomAutoConfiguration,\ com.example.AnotherAutoConfiguration ``` ##### 方法二:检查注解是否冗余 避免在同一项目中多次引入相同的功能注解。通常情况下,仅需保留 `@SpringBootApplication` 即可完成大部分初始化工作。如果有额外需求,可以通过扩展的方式实现而非堆叠注解[^1]。 ##### 方法三:验证加载机制 尝试打印日志以确认 `spring.factories` 是否被成功加载以及其中的内容是否按预期解析。可以在启动阶段增加调试信息来排查问题: ```java import org.springframework.core.io.support.SpringFactoriesLoader; public class FactoryTest { public static void main(String[] args) { List<String> factories = SpringFactoriesLoader.loadFactoryNames( EnableAutoConfiguration.class, Thread.currentThread().getContextClassLoader()); System.out.println(factories); } } ``` 此代码片段可以帮助定位哪些实际上已被加载。 ##### 方法四:清理依赖冲突 使用 Maven 或 Gradle 工具检测是否存在版本冲突。对于 Maven 用户,可通过命令 `mvn dependency:tree` 查看完整的依赖树结构;Gradle 则可以执行 `gradle dependencies` 来获得相似的结果。一旦发现冲突,应优先选用与当前 Spring Boot 版本兼容的依赖组合[^5]。 ##### 方法五:重新审视配置的设计 确保所有写入 `spring.factories` 的都实现了必要的接口并且具备无参构造函数。此外,建议将复杂的业务逻辑封装至单独的服务层而不是直接嵌套于配置内部[^3]。 --- ### 总结 针对 `spring.factories` 配置未生效的问题,可以从以下几个方面入手解决:校验资源文件的位置及其语法准确性、审查是否有不必要的重复标注干扰正常流程、借助工具诊断潜在的装载异常现象、统一管理外部库之间的协作关系最后再优化具体实现细节上的不足之处。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

镜悬xhs

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值