现象

在阅读 Spring-Boot 相关源码时,常常见到 spring.factories 文件,里面写了自动配置(AutoConfiguration)相关的类名,因此产生了一个疑问:“明明自动配置的类已经打上了 @Configuration 的注解,为什么还要写 spring.factories 文件?

用过 Spring Boot 的都知道

@ComponentScan 注解的作用是扫描 @SpringBootApplication 所在的 Application 类所在的包(basepackage)下所有的 @Component 注解(或拓展了 @Component 的注解)标记的 bean,并注册到 spring 容器中。

那么问题来了

在 Spring Boot 项目中,如果你想要被 Spring 容器管理的 bean 不在 Spring Boot 包扫描路径下,怎么办?

解决 Spring Boot 中不能被默认路径扫描的配置类的方式,有 2 种:

(1)在 Spring Boot 主类上使用 @Import 注解
(2)使用 spring.factories 文件

以下是对 使用 spring.factories 文件的简单理解

Spring Boot 的扩展机制之 Spring Factories

Spring Boot 中有一种非常解耦的扩展机制:Spring Factories。这种扩展机制实际上是仿照Java中的SPI扩展机制来实现的。

什么是 SPI 机制?

SPI 的全名为 Service Provider Interface.大多数开发人员可能不熟悉,因为这个是针对厂商或者插件的。在java.util.ServiceLoader的文档里有比较详细的介绍。

简单的总结下 java SPI 机制的思想。我们系统里抽象的各个模块,往往有很多不同的实现方案,比如日志模块的方案,xml解析模块、jdbc模块的方案等。面向的对象的设计里,我们一般推荐模块之间基于接口编程,模块之间不对实现类进行硬编码。一旦代码里涉及具体的实现类,就违反了可拔插的原则,如果需要替换一种实现,就需要修改代码。为了实现在模块装配的时候能不在程序里动态指明,这就需要一种服务发现机制。

java SPI 就是提供这样的一个机制:为某个接口寻找服务实现的机制。有点类似IOC的思想,就是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要。

Spring Boot 中的 SPI 机制

在 Spring 中也有一种类似与 Java SPI 的加载机制。它在 resources/META-INF/spring.factories 文件中配置接口的实现类名称,然后在程序中读取这些配置文件并实例化。

这种自定义的SPI机制是 Spring Boot Starter 实现的基础。

Spring Factories 实现原理是什么?

spring-core 包里定义了 SpringFactoriesLoader 类,这个类实现了检索 META-INF/spring.factories 文件,并获取指定接口的配置的功能。在这个类中定义了两个对外的方法:

loadFactories 根据接口类获取其实现类的实例,这个方法返回的是对象列表。
loadFactoryNames 根据接口获取其接口类的名称,这个方法返回的是类名的列表。

上面的两个方法的关键都是从指定的 ClassLoader 中获取 spring.factories 文件,并解析得到类名列表,具体代码如下

public final class SpringFactoriesLoader {public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";private static final Log logger = LogFactory.getLog(SpringFactoriesLoader.class);private static final Map<ClassLoader, MultiValueMap<String, String>> cache = new ConcurrentReferenceHashMap();private SpringFactoriesLoader() {}public static <T> List<T> loadFactories(Class<T> factoryClass, @Nullable ClassLoader classLoader) {Assert.notNull(factoryClass, "'factoryClass' must not be null");ClassLoader classLoaderToUse = classLoader;if (classLoader == null) {classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();}List<String> factoryNames = loadFactoryNames(factoryClass, classLoaderToUse);if (logger.isTraceEnabled()) {logger.trace("Loaded [" + factoryClass.getName() + "] names: " + factoryNames);}List<T> result = new ArrayList(factoryNames.size());Iterator var5 = factoryNames.iterator();while(var5.hasNext()) {String factoryName = (String)var5.next();result.add(instantiateFactory(factoryName, factoryClass, classLoaderToUse));}AnnotationAwareOrderComparator.sort(result);return result;}public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {String factoryClassName = factoryClass.getName();return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());}private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);if (result != null) {return result;} else {try {Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");LinkedMultiValueMap result = new LinkedMultiValueMap();while(urls.hasMoreElements()) {URL url = (URL)urls.nextElement();UrlResource resource = new UrlResource(url);Properties properties = PropertiesLoaderUtils.loadProperties(resource);Iterator var6 = properties.entrySet().iterator();while(var6.hasNext()) {Entry<?, ?> entry = (Entry)var6.next();String factoryClassName = ((String)entry.getKey()).trim();String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());int var10 = var9.length;for(int var11 = 0; var11 < var10; ++var11) {String factoryName = var9[var11];result.add(factoryClassName, factoryName.trim());}}}cache.put(classLoader, result);return result;} catch (IOException var13) {throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);}}}private static <T> T instantiateFactory(String instanceClassName, Class<T> factoryClass, ClassLoader classLoader) {try {Class<?> instanceClass = ClassUtils.forName(instanceClassName, classLoader);if (!factoryClass.isAssignableFrom(instanceClass)) {throw new IllegalArgumentException("Class [" + instanceClassName + "] is not assignable to [" + factoryClass.getName() + "]");} else {return ReflectionUtils.accessibleConstructor(instanceClass, new Class[0]).newInstance();}} catch (Throwable var4) {throw new IllegalArgumentException("Unable to instantiate factory class: " + factoryClass.getName(), var4);}}
}

从代码中我们可以知道,在这个方法中会遍历整个 spring-boot 项目的 classpath 下 ClassLoader 中所有 jar 包下的 spring.factories文件。也就是说我们可以在自己的 jar 中配置 spring.factories 文件,不会影响到其它地方的配置,也不会被别人的配置覆盖。

Spring Factories 在 Spring Boot 中的应用

在 Spring Boot 的很多包中都能够找到 spring.factories 文件,接下来我们以 spring-boot-autoconfigure 包为例进行介绍

# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer# Auto Configuration Import Listeners
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
org.springframework.boot.autoconfigure.condition.OnClassCondition,\
org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration

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

spring.factories 的妙用相关推荐

  1. SpringBoot解耦的扩展机制 Spring Factories介绍及使用

    一.什么是 SPI机制 Spring Boot中有一种非常解耦的扩展机制:Spring Factories.这种扩展机制实际上是仿照Java中的SPI扩展机制来实现的.SPI的全名为Service P ...

  2. Spring Boot 之spring.factories

    首先抛出一个问题:如果想要被Spring容器管理的Bean的路径不再Spring Boot 的包扫描路径下,怎么办呢?也就是如何去加载第三方的Bean 呢? 有两种方式可以解决: 这里我们使用Swag ...

  3. openfeign远程调用不起作用解决_使用Spring Boot的spring.factories进行注入---SpringCloud Alibaba_若依微服务框架改造---工作笔记007

    我们在若依的: ruoyi-api-system模块中,可以看到在 com.ruoyi.system.api包下,有各种的 RemoteUserService等,然后我防着写了个自己的,但是发现,调用 ...

  4. Spring Boot 之 spring.factories的用法

    原因 为什么要使用,因为在程序开发中,可能包名不一样,pom依赖的很多的jar 他们是如何把这些类进行注入到spring容器中的呢. 所以springboot就提出了spring.factories ...

  5. SpringBoot加载spring.factories的价值

    SpringBoot加载spring.factories的价值 在springboot的各个依赖包下,我们经常看到META-INF/spring.factories这个文件.spring.factor ...

  6. spring.factories 的基本使用

    spring.factories 的基本作用 在若依-微服务版的源码学习中,发现项目中多次用到了 spring.factories .因此记录一下自己的学习总结: 参考博客: spring.facto ...

  7. spring.factories详解

    在Spring Boot中有一种非常解耦的扩展机制:Spring Factories.这种扩展机制实际上是仿照Java中的SPI扩展机制来实现的. Java SPI机制SPI的全名为Service P ...

  8. Spring Factories机制

    Spring Factories机制简述 Spring Factories机制和Java SPI的扩展机制类似,Spring Boot采用了spring.factories的扩展机制,在很多sprin ...

  9. 简单介绍【spring.factories】的使用

    前言 starter的使用在springboot项目中可以说是非常常见,可能有些朋友在使用springboot项目的时候只是在网络上找了一篇如何创建一个springboot,如何又和mybatis或m ...

最新文章

  1. 摄像头PVD和CVD薄膜
  2. iis7 文件服务器搭建,iis7 ftp服务器搭建
  3. java sftp nologin_SFTP连接通过Java询问奇怪的身份验证
  4. 【pip install psycopg2安装报错】Error: pg_config executable not found.
  5. 【ARM】Tiny4412裸板编程之MMU(页 4K)
  6. java –cp ./:_成为Java流专家–第2部分:中级操作
  7. node.js简单爬虫
  8. ORA-00911:无效字符 错误及解决
  9. pep3评估报告解读_首次公布!PISA全球胜任能力评估报告出炉,有何新启示?
  10. WPF Popup 相关内容
  11. 【Kafka】nable to write to standard out, closing consumer Console consumer process hangs on SIGINT
  12. 财务报表越做越丑?这些秒杀Excel的可视化工具,人人都能用
  13. CSS3实现卡片翻转动画
  14. 剑指offer——面试题28:字符串的排列
  15. Netflix继续开源,更多猴子进入视野
  16. oracle12c cdb修改,Oracle 12C CDB字符集修改
  17. 华为国产系统Android,国产手机系统即将出现!华为将抛弃安卓:成功研发自有手机系统...
  18. 解释一下什么是鲁棒性
  19. python归一化 增大差异_Python实现描述性统计
  20. 国密SM2的证书制作及验证

热门文章

  1. 瘦子的肠道菌群和胖子的区别_瘦子和病态肥胖患者肠道菌群组成和潜在功能的显著差异...
  2. 计算机编程方程求解的步骤,计算机解决问题的过程PPT学习课件
  3. [转载] java程序员快速学c++
  4. 日期setMinutes()方法以及JavaScript中的示例
  5. sql2008能否打开mysql数据库_mysql数据库数据能不能导入到sql server中
  6. 二维的完整形式是什么?
  7. sql更改完整模式报错_SQL的完整形式是什么?
  8. 「视频版」当线程池溢出之后,程序会奔溃吗?面试突击 007 期
  9. TextArea里Placeholder换行问题
  10. JSP JAVA 自定义 错误页面(404,505,500)