一、SPI源码分析
为什么要讲SPI呢?因为在SpringBoot的自动装配中其实有使用到SPI机制,所以掌握了这部分对于SpringBoot的学习还是很有帮助的。
SPI,全称为Service Provider Interface,是一种服务发现机制。它通过在ClassPath路径下的META-INF/services文件夹查找文件,自动加载文件里所定义的类。这一机制为很多框架扩展提供了可能,比如在Dubbo、JDBC中都使用到了SPI机制。我们先通过一个很简单的例子来看下它是怎么用的。


源码分析
关键组件解析
-
ServiceLoader.load()方法:
scssjava 复制 public static <S> ServiceLoader<S> load(Class<S> service) { ClassLoader cl = Thread.currentThread().getContextClassLoader(); return ServiceLoader.load(service, cl); }
- 核心方法,负责加载指定接口的所有实现
- 默认使用当前线程的上下文类加载器
- 内部维护了一个缓存机制,避免重复加载
-
providers.iterator() :
返回一个迭代器,该迭代器封装了底层的服务提供者发现和实例化逻辑。
-
内部工作机制:
- ServiceLoader在类路径下查找
META-INF/services/[全限定接口名]
文件 - 解析该文件中列出的实现类全限定名
- 使用无参构造函数实例化这些实现类
- 按顺序返回这些实例
- ServiceLoader在类路径下查找
工作流程
- JVM加载主类并执行main方法
- 调用
ServiceLoader.load(BaseData.class)
开始服务发现过程 - ServiceLoader在类路径中搜索
META-INF/services/[BaseData的全限定名]
文件 - 解析文件中列出的所有实现类
- 实例化每个实现类并通过迭代器提供访问
- 遍历迭代器,对每个服务提供者调用
baseURL()
方法
注意事项
- 代码中没有处理
baseURL()
的返回值 - 缺少异常处理机制
- ServiceLoader的实例化是懒加载的,只有在迭代时才会创建实例
- ServiceLoader使用无参构造函数实例化服务提供者,因此实现类必须有无参构造函数
这种模式是Java中实现可插拔架构的标准方式,允许第三方为应用程序提供功能扩展,而不需要修改核心代码。
自动装配源码分析
在前面的分析中,Spring Framework一直在致力于解决一个问题,就是如何让bean的管理变得更简单,如何让开发者尽可能的少关注一些基础化的bean的配置,从而实现自动装配。所以,所谓的自动装配,实际上就是如何自动将bean装载到ioc容器中来。
实际上在spring 3.x版本中,Enable模块驱动注解的出现,已经有了一定的自动装配的雏形,而真正能够实现这一机制,还是在spring 4.x版本中,conditional条件注解的出现。ok,我们来看一下spring boot的自动装配是怎么回事。
自动装配的演示
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
yaml
spring:
redis:
host: 127.0.0.1
port: 6379
typescript
@Autowired
private RedisTemplate<String,String> redisTemplate;
按照下面的顺序添加starter,然后添加配置,使用RedisTemplate就可以使用了?那大家想没想过一个问题,为什么RedisTemplate可以被直接注入?它是什么时候加入到ioc容器的呢?这就是自动装配。自动装配可以使得classpath下依赖的包相关的bean,被自动装载到Spring ioc容器中,怎么做到的呢?
ImportBeanDefinitionRegistrar 与 ImportSelector 核心区别
-
ImportSelector
通过返回类名数组间接导入配置类,Spring 后续会自动处理这些类的加载和 Bean 注册 适用场景:批量导入配置类(如 Spring Boot 自动配置)。
-
ImportBeanDefinitionRegistrar
直接通过代码向容器注册 Bean 定义,支持动态生成 Bean 属性甚至覆盖现有定义 适用场景:需要精细控制 Bean 定义的场景(如动态数据源注册)。
SpringBoot自动装配机制
启动类注解 @SpringBootApplication
@EnableAutoConfiguration
@Import({AutoConfigurationImportSelector.class})
Springboot @Enable*注解的工作原理:ImportSelector接口selectImports返回的数组(类的全类名)都会被加载到spring容器中。
kotlin
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
//获取所有候选配置类EnableAutoConfiguration
AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}
kotlin
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
} else {
//获取元注解中的属性
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
//使用SpringFactoriesLoader加载ClassPath路径下META-INF\spring.factories中
//key=org.springframework.boot.autoconfigure.EnableAutoConfiguration
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
//去重
configurations = this.<String>removeDuplicates(configurations);
//应用exclusion属性
Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
this.checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
//过滤,检查候选配置类上的注解@ConditionalOnClass,如果要求的类不存在,则这个候选类会被过滤不被加载
configurations = this.getConfigurationClassFilter().filter(configurations);
//广播事件
this.fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
}
本质上来说,其实EnableAutoConfiguration会帮助SpringBoot应用把所有符合@Configuration配置都加载到当前SpringBoot创建的IoC容器,而这里面借助了Spring框架提供的一个工具类SpringFactoriesLoader的支持。以及用到了Spring提供的条件注解@Conditional,选择性的针对需要加载的bean进行条件过滤。
假设我们有一个自定义的 Redis Starter,希望 仅在项目中存在 RedisTemplate
类时 自动配置 Redis 客户端。此时可以通过 spring-autoconfigure-metadata.properties
定义条件元数据。
步骤 1:创建配置类
java
java
复制
// src/main/java/com/example/RedisAutoConfig.java
@Configuration
@ConditionalOnClass(RedisTemplate.class) // 条件注解
public class RedisAutoConfig {
@Bean
public RedisClient redisClient() {
return new RedisClient("localhost", 6379);
}
}
步骤 2:生成条件元数据
编译项目时,spring-boot-autoconfigure-processor
会自动解析 @ConditionalOnClass
注解,并在生成的 spring-autoconfigure-metadata.properties
中添加:
ini
properties
复制
# 自动生成的内容(位于编译后的 JAR 包的 META-INF 目录)
com.example.RedisAutoConfig=org.springframework.boot.autoconfigure.condition.ConditionalOnClass=org.springframework.data.redis.core.RedisTemplate
步骤 3:验证条件过滤
- 情况 1:项目中引入了 Redis 依赖
类路径存在RedisTemplate
→ 配置类生效,RedisClient
Bean 被注册。 - 情况 2:未引入 Redis 依赖
类路径无RedisTemplate
→ 配置类被过滤,RedisClient
Bean 不存在。
手动定义元数据(可选)
若需自定义条件(如不依赖注解),可直接在 spring-autoconfigure-metadata.properties
中添加:
ini
properties
复制
com.example.RedisAutoConfig=org.springframework.boot.autoconfigure.condition.ConditionalOnClass=org.springframework.data.redis.core.RedisTemplate
关键点解析
-
条件表达式格式
类名.条件类型=条件值
,其中:条件类型
:如ConditionalOnClass
、ConditionalOnProperty
。条件值
:具体条件参数(如类名、属性键值对)。
-
自动与手动结合
- 推荐通过注解自动生成元数据,减少手动维护成本。
- 手动定义适用于复杂条件或旧代码兼容。
调试验证
在启动日志中观察以下内容,确认条件是否生效:
ini
log
复制
DEBUG AutoConfigurationMetadataLoader - Loading metadata from [META-INF/spring-autoconfigure-metadata.properties]
DEBUG AutoConfigurationImportFilter - Condition evaluation: com.example.RedisAutoConfig (ConditionalOnClass=RedisTemplate) → true
总结
通过 spring-autoconfigure-metadata.properties
,Spring Boot 实现了 按条件动态加载配置类 的核心逻辑。开发者只需关注条件本身,无需手动实现复杂的过滤代码。