Spring Factories知多少

Spring Factories知多少?


一柱独擎梁栋重,十年整顿乾坤了。

种春风、桃李满人间,知多少。

Spring Factories使用场景

​ spring.factories文件主要是帮助把spring-boot项目包以外的bean(即在pom文件中添加依赖中的bean)注册到spring-boot项目的spring容器。由于@ComponentScan注解只能扫描spring-boot项目包内的bean并注册到spring容器中,因此需要@EnableAutoConfiguration注解来注册项目包外的bean。而spring.factories文件,则是用来记录项目包外需要注册的bean类名。

Spring Factories是什么

​ 在Spring中有一种类似与Java SPI的加载机制。它在META-INF/spring.factories文件中配置接口的实现类名称,然后在程序中读取这些配置文件并实例化。这种自定义的SPI机制是Spring Boot Starter实现的基础。简而言之Spring Factories就是Spring Boot中一种解耦的扩展机制。


META-INF/spring.factories文件是springboot 框架识别并解析starter的核心文件,了解springboot加载META-INF/spring.factories文件原理至关重要。

Spring Factories原理

run方法源码
	/**
	 * Run the Spring application, creating and refreshing a new
	 * {@link ApplicationContext}.
	 * @param args the application arguments (usually passed from a Java main method)
	 * @return a running {@link ApplicationContext}
	 */
	public ConfigurableApplicationContext run(String... args) {
		 //创建程序计时器
        StopWatch stopWatch=new StopWatch();
        //启动程序计时器
        stopWatch.start();
        ConfigurableApplicationContext context=null;
        Collection<SpringBootExceptionReporter> exceptionReporters=new ArrayList<>();
        //设置java.awt.headless系统属性值
        configureHeadlessProperty();
        //从缓存的META-INF/spring.factories Map中获取
        //SpringApplicationRunListeners接口的所有子类
        SpringApplicationRunListeners listeners=getRunListeners(args);
        //回调通知应用开始启动事件监听器
		listeners.starting();
		try {
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
			ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
			configureIgnoreBeanInfo(environment);
			Banner printedBanner = printBanner(environment);
			context = createApplicationContext();
			exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
					new Class[] { ConfigurableApplicationContext.class }, context);
			prepareContext(context, environment, listeners, applicationArguments, printedBanner);
			refreshContext(context);
			afterRefresh(context, applicationArguments);
			stopWatch.stop();
			if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
			}
			listeners.started(context);
			callRunners(context, applicationArguments);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, exceptionReporters, listeners);
			throw new IllegalStateException(ex);
		}

		try {
			listeners.running(context);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, exceptionReporters, null);
			throw new IllegalStateException(ex);
		}
		return context;
	}

getRunListeners(args)源码
private SpringApplicationRunListeners getRunListeners(String[] args) {
 
 
   //SpringApplicationRunListener实现类构造器参数类型数组
   //默认实现类为EventPublishingRunListener
   Class<?>[] types = new Class<?>[]{SpringApplication.class, String[].class};
 
 
   //将SpringApplicationRunListener实现类封装到SpringApplicationRunListeners
   //SpringApplicationRunListeners支持通过for所有事件list批量执行功能
//后面分析starting方法时可以看到for代码
   return new SpringApplicationRunListeners(logger,
         getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
}
 
 
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
   
   //需要查找的接口名称字符串
   String factoryClassName = factoryClass.getName();
   
   //读取磁盘文件进行加载,返回找到封装结果的Map中key为需要查找的接口名称的values
   return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}
 
 
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
 
 
   //先从缓存查找当前classloader是否加载过
   MultiValueMap<String, String> result = cache.get(classLoader);
   if (result != null) {
      //如果之前加载过就返回,这里体验了读取文件资源实际做了缓存的
      //后面都是从缓存读取
      return result;
   }
 
 
   try {
 
 
      //从当前classloader的classpath下读取META-INF/spring.factories
      //封装返回Enumeration迭代器
      Enumeration<URL> urls = (classLoader != null ?
            classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
            ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
      result = new LinkedMultiValueMap<>();
 
 
      //迭代所有META-INF/spring.factories文件
      while (urls.hasMoreElements()) {
         URL url = urls.nextElement();
         //将META-INF/spring.factories文件转为UrlResource对象
         UrlResource resource = new UrlResource(url);
         //将META-INF/spring.factories文件从UrlResource对象转为Properties映射表
         Properties properties = PropertiesLoaderUtils.loadProperties(resource);
         //遍历META-INF/spring.factories文件entry
         for (Map.Entry<?, ?> entry : properties.entrySet()) {
            String factoryClassName = ((String) entry.getKey()).trim();
            
            //遍历value逗号分隔返回的String[]
            for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
 
 
               //放到LinkedMultiValueMap,key为接口全名称字符串,
               //value为接口实现类的全名称字符串
               result.add(factoryClassName, factoryName.trim());
            }
         }
      }
      
      //保存到ConcurrentReferenceHashMap,key为当前classloader
      //value为所有META-INF/spring.factories文件k,v Map
      cache.put(classLoader, result);
      return result;
   }
   catch (IOException ex) {
      throw new IllegalArgumentException("Unable to load factories from location [" +
            FACTORIES_RESOURCE_LOCATION + "]", ex);
   }
}
 
 
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;
}

大致展示了springboot读取META-INF/spring.factories过程。通过classloader从classpath下读取所有的META-INF/spring.factories文件,然后通过Map键值对数据结构保存在spring core模块下的SpringFactoriesLoader抽象类的静态属性cache中。后续所有需要从META-INF/spring.factories文件获取都是尝试先从这个缓存的Map中获取。

JAVA的SPI机制

SPI的全名为Service Provider Interface.大多数开发人员可能不熟悉,因为这个是针对厂商或者插件的。在java.util.ServiceLoader的文档里有比较详细的介绍。
简单的总结下java SPI机制的思想。我们系统里抽象的各个模块,往往有很多不同的实现方案,比如日志模块的方案,xml解析模块、jdbc模块的方案等。面向的对象的设计里,我们一般推荐模块之间基于接口编程,模块之间不对实现类进行硬编码。一旦代码里涉及具体的实现类,就违反了可拔插的原则,如果需要替换一种实现,就需要修改代码。为了实现在模块装配的时候能不在程序里动态指明,这就需要一种服务发现机制。
java SPI就是提供这样的一个机制:为某个接口寻找服务实现的机制。有点类似IOC的思想,就是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要。

Spring Factories使用

​ 封装好的sdk,recourse中添加对应配置文件,将maven坐标配置到指定项目,spring boot自动配置会加载pom中的额外bean,简单方便。

​ 有一些封装的案例可以参考下。

//META-INF   spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
    org.nom.mds.spring.boot.autoconfigure.DynamicDataSourceAutoConfiguration
<think>好的,用户问的是“spring.factories与spi的关系”,我需要先回顾一下之前关于Java SPI的讨论,然后联系到Spring的机制。 首先,用户之前已经了解了Java SPI,知道它是通过META-INF/services下的文件来注册服务实现的。现在他们想了解Springspring.factories和SPI之间的关系。这说明用户可能在使用Spring时遇到了自动配置的问题,或者想深入理解Spring Boot的自动配置原理。 接下来,我需要明确spring.factories的作用。在Spring Boot中,spring.factories文件用于自动配置,通常位于META-INF目录下,用来声明各种配置类。它实际上是SpringJava SPI机制的一种扩展和增强。 需要解释两者的相同点和不同点。相同点在于它们都使用配置文件来解耦接口和实现,实现动态加载。不同点在于,Java SPI需要接口全限定名作为文件名,而spring.factories使用统一的文件,通过键值对的方式组织,支持多值,更灵活。此外,Spring的机制在加载时机和功能扩展上更强大,比如支持自动配置、监听器等。 用户可能的深层需求是理解Spring Boot自动配置背后的机制,或者如何自定义扩展Spring Boot的功能。因此,在回答时需要结合具体例子,比如自动配置类是如何被加载的,以及如何通过spring.factories添加自定义配置。 还要注意比较两者,帮助用户区分它们的应用场景。例如,Java SPI适用于标准化的服务扩展,而Spring的机制更适合框架内部的自动配置和扩展。此外,需要提到Spring Boot 2.7后的变化,使用META-INF/spring/下的新文件,但原理相似,以保持信息的更新。 最后,确保结构清晰,分点说明,让用户能够逐步理解两者的关系,以及Spring如何基于SPI思想进行了优化和扩展。同时,保持语言简洁,避免技术术语过多,必要时举例说明。</think>### `spring.factories` 与 Java SPI 的关系 `spring.factories` 是 Spring BootJava SPI 机制的扩展和增强,核心目标类似(**解耦接口与实现,实现动态加载**),但设计上更灵活,适用于 Spring 生态的自动配置与组件扩展。 --- ### 核心联系与区别 | **特性** | **Java SPI** | **Spring Boot `spring.factories`** | |-------------------|----------------------------------|-----------------------------------------| | **配置文件** | `META-INF/services/接口全限定名` | `META-INF/spring.factories` | | **配置格式** | 每行一个实现类全限定名 | Key-Value 格式(键为接口,值为实现类列表)| | **加载方式** | `ServiceLoader.load()` | `SpringFactoriesLoader.loadFactories()` | | **功能扩展** | 简单服务发现 | 支持自动配置、监听器、初始化器等复杂场景 | | **加载时机** | 按需加载 | 应用启动时预加载 | --- ### `spring.factories` 的核心作用 1. **Spring Boot 自动配置的基石** - 通过 `org.springframework.boot.autoconfigure.EnableAutoConfiguration` 键声明自动配置类。 - 例如: ```properties org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.example.MyAutoConfiguration ``` - Spring Boot 启动时自动加载这些配置类,完成 Bean 的注册与条件化配置。 2. **扩展 Spring 功能** - 定义多种扩展点(如 `ApplicationListener`, `EnvironmentPostProcessor`)。 - 示例:注册自定义环境处理器 ```properties org.springframework.boot.env.EnvironmentPostProcessor=\ com.example.CustomEnvPostProcessor ``` 3. **模块化开发支持** - 第三方 Jar 包通过 `spring.factories` 向 Spring 容器“声明”自身功能,无需主程序显式依赖。 --- ### 与 Java SPI 的共性 1. **解耦思想** - 两者均通过配置文件实现接口与实现的分离,避免硬编码依赖。 2. **动态加载** - 运行时根据配置加载实现类,支持“开箱即用”的扩展能力。 3. **标准化路径** - 配置文件均放置在 `META-INF/` 目录下,约定大于配置。 --- ### Spring 对 SPI 的增强 1. **更灵活的键值对格式** - 一个文件支持多组扩展点(如同时配置自动配置类、监听器等)。 - 示例: ```properties org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.example.A,\ com.example.B org.springframework.context.ApplicationListener=com.example.MyListener ``` 2. **支持批量加载** - 通过 `SpringFactoriesLoader.loadFactories()` 一次性加载某接口的所有实现类。 - 代码示例: ```java List<MyStrategy> strategies = SpringFactoriesLoader.loadFactories( MyStrategy.class, classLoader ); ``` 3. **与 Spring 上下文深度集成** - 自动配置类可结合 `@Conditional` 注解实现条件化加载(如根据类路径是否存在决定是否启用配置)。 --- ### 典型应用场景 1. **Spring Boot Starter 开发** - 自定义 Starter 通过 `spring.factories` 声明自动配置类,实现依赖组件的“零配置接入”。 2. **框架扩展** - 扩展 Spring 事件监听、环境处理等底层机制(如 Apollo 配置中心通过 `EnvironmentPostProcessor` 动态加载配置)。 3. **插件化架构** - 主程序通过 `spring.factories` 发现插件模块,无需修改代码即可动态增强功能。 --- ### 演进与替代方案 - **Spring Boot 2.7+ 的调整** Spring Boot 2.7 引入了 `META-INF/spring/` 目录下的新文件(如 `org.springframework.boot.autoconfigure.AutoConfiguration.imports`)替代部分 `spring.factories` 功能,但核心逻辑仍延续 SPI 思想。 --- ### 总结 `spring.factories` 是 Spring Boot **基于 Java SPI 思想设计的增强版服务发现机制**,通过统一的配置文件和键值对格式,实现了更复杂的扩展场景(如自动配置、组件注册)。理解其原理可帮助你掌握 Spring Boot 自动配置的底层逻辑,并灵活定制框架行为。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值