Spring Boot 自动装配原理揭秘:@Conditional 注解全家桶详解
摘要 :Spring Boot 的自动装配(Auto-Configuration)是其"约定优于配置"哲学的核心实现。本文深入源码层面,从
@EnableAutoConfiguration的加载机制、SpringFactoriesLoader 的 SPI 扩展、到@Conditional条件注解的完整家族,逐一拆解自动装配的决策流程,并演示如何编写自定义 Starter。关键词:Spring Boot、自动装配、Auto-Configuration、@Conditional、SpringFactoriesLoader、自定义 Starter、条件注解
一、自动装配的入口:@EnableAutoConfiguration
1.1 从 @SpringBootApplication 说起
每一个 Spring Boot 应用的主类上,我们都能看到这个熟悉的组合注解:
java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class)
})
public @interface SpringBootApplication {
}
@EnableAutoConfiguration 是自动装配的开关。它的核心定义:
java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
}
关键在 @Import(AutoConfigurationImportSelector.class) ------ 这是自动装配的触发点。
1.2 AutoConfigurationImportSelector 的加载机制
AutoConfigurationImportSelector 实现了 DeferredImportSelector 接口,这决定了它的特殊加载时机:
- 延迟加载 :在所有普通
@Configuration类解析之后、Bean 定义注册之前执行 - 分组处理 :属于
AutoConfigurationGroup,确保同类配置集中处理
核心流程(selectImports() 方法):
java
public class AutoConfigurationImportSelector implements DeferredImportSelector {
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
// 1. 检查自动装配是否被禁用(spring.boot.enableautoconfiguration=false)
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
// 2. 从 META-INF/spring-autoconfigure-metadata.properties 读取元数据
AutoConfigurationMetadata autoConfigurationMetadata =
AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
// 3. 通过 SpringFactoriesLoader 获取所有候选配置类
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
// 4. 去重、排序、排除、过滤
configurations = removeDuplicates(configurations);
configurations = sort(configurations, autoConfigurationMetadata);
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
configurations = removeAll(configurations, exclusions);
configurations = filter(configurations, autoConfigurationMetadata);
// 5. 触发自动装配事件,便于监听器介入
fireAutoConfigurationImportEvents(configurations, exclusions);
return StringUtils.toStringArray(configurations);
}
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
AnnotationAttributes attributes) {
// 核心:加载 META-INF/spring.factories 中 EnableAutoConfiguration 对应的值
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()
);
Assert.notEmpty(configurations,
"No auto configuration classes found in META-INF/spring.factories"
);
return configurations;
}
}
二、SpringFactoriesLoader:SPI 机制的基石
2.1 META-INF/spring.factories 的结构
spring.factories 是 Spring Boot 的 SPI(Service Provider Interface)实现文件,采用 Properties 格式:
properties
# Spring Boot 2.7+ 推荐使用 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
# 但 spring.factories 仍兼容
# 自动装配配置类列表
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\
com.example.mystarter.MyCustomAutoConfiguration
# 其他 SPI 扩展点
org.springframework.boot.diagnostics.FailureAnalyzer=\
com.example.mystarter.MyFailureAnalyzer
org.springframework.boot.env.EnvironmentPostProcessor=\
com.example.mystarter.MyEnvironmentPostProcessor
2.2 Spring Boot 2.7+ 的新文件格式
为了避免 spring.factories 文件过于庞大,Spring Boot 2.7 引入了新的文件格式:
META-INF/spring/
└── org.springframework.boot.autoconfigure.AutoConfiguration.imports
内容变为纯文本列表(每行一个配置类):
com.example.mystarter.autoconfigure.MyCustomAutoConfiguration
com.example.mystarter.autoconfigure.MyDataSourceAutoConfiguration
兼容性 :
spring.factories中的EnableAutoConfiguration键仍然有效,但新格式更清晰,且支持按配置类单独管理。Spring Boot 3.x 建议全面迁移到新格式。
三、@Conditional 条件注解全家桶
自动装配的核心决策逻辑是:在候选配置类中,哪些应该被实际加载?这由 @Conditional 注解家族决定。
3.1 条件注解的通用机制
@Conditional 本身是一个元注解,接受一个 Condition 接口实现类:
java
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
Class<? extends Condition>[] value();
}
Condition 接口定义:
java
@FunctionalInterface
public interface Condition {
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
ConditionContext:提供 BeanDefinitionRegistry、Environment、ResourceLoader 等上下文AnnotatedTypeMetadata:获取注解的元数据信息
3.2 内置条件注解详解
| 注解 | 条件 | 典型使用场景 |
|---|---|---|
@ConditionalOnClass |
类路径存在指定类 | 依赖库存在时启用配置(如 DataSource 存在时启用 JPA) |
@ConditionalOnMissingClass |
类路径不存在指定类 | 避免与旧版本库冲突 |
@ConditionalOnBean |
容器中存在指定 Bean | 某数据源已配置时启用 DAO 层 |
@ConditionalOnMissingBean |
容器中不存在指定 Bean | 提供默认实现,允许用户自定义覆盖 |
@ConditionalOnProperty |
配置属性匹配指定值 | 通过 application.yml 开关控制功能 |
@ConditionalOnResource |
类路径存在指定资源 | 配置文件存在时启用特定功能 |
@ConditionalOnWebApplication |
当前是 Web 应用 | 区分 Servlet / Reactive 环境 |
@ConditionalOnNotWebApplication |
当前不是 Web 应用 | 批处理、CLI 工具等场景 |
@ConditionalOnExpression |
SpEL 表达式为 true | 复杂逻辑条件判断 |
@ConditionalOnJava |
匹配的 Java 版本 | 利用 Java 新特性的配置 |
@ConditionalOnJndi |
JNDI 存在指定位置 | 传统 EE 容器兼容 |
@ConditionalOnSingleCandidate |
指定类型只有一个候选 Bean | 消除歧义,自动选择唯一实现 |
@ConditionalOnWarDeployment |
以 WAR 方式部署 | 嵌入式 vs 外置容器区分 |
3.3 条件注解源码解析
以 @ConditionalOnClass 为例:
java
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnClassCondition.class)
public @interface ConditionalOnClass {
Class<?>[] value() default {}; // 类引用(编译时安全)
String[] name() default {}; // 类名字符串(避免 ClassNotFoundException)
}
对应的 OnClassCondition 实现:
java
@Order(Ordered.HIGHEST_PRECEDENCE) // 最高优先级,优先过滤
class OnClassCondition extends FilteringSpringBootCondition {
@Override
protected ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses,
AutoConfigurationMetadata autoConfigurationMetadata) {
// 使用 ASM 读取 .class 文件,避免直接加载类
// 这样可以在不触发 ClassLoader 的情况下检查类是否存在
List<ConditionOutcome> outcomes = new ArrayList<>(autoConfigurationClasses.length);
for (String autoConfigurationClass : autoConfigurationClasses) {
ConditionOutcome outcome = null;
if (autoConfigurationClass != null) {
String candidates = autoConfigurationMetadata.get(autoConfigurationClass, "ConditionalOnClass");
if (candidates != null) {
outcome = getOutcome(candidates);
}
}
outcomes.add(outcome);
}
return outcomes.toArray(new ConditionOutcome[0]);
}
private ConditionOutcome getOutcome(String candidates) {
try {
List<String> classNames = Arrays.asList(StringUtils.commaDelimitedListToStringArray(candidates));
for (String className : classNames) {
if (!ClassNameFilter.MATCH.match(className, getBeanClassLoader())) {
return ConditionOutcome.noMatch("@ConditionalOnClass missing " + className);
}
}
} catch (Exception ex) {
// 处理异常
}
return null; // 所有类都存在,条件匹配
}
}
关键设计 :
OnClassCondition使用ClassNameFilter进行 ASM 字节码扫描 ,而非Class.forName()。这样做的原因是:如果直接加载类,可能会触发该类的静态初始化,甚至引发NoClassDefFoundError。ASM 扫描只读取类名,不加载类,更安全。
3.4 组合条件:AnyCondition 与 AllNestedConditions
Spring Boot 也支持条件组合:
java
@ConditionalOnClass(DataSource.class)
@ConditionalOnBean(DataSource.class)
@ConditionalOnProperty(prefix = "app.mybatis", name = "enabled", havingValue = "true", matchIfMissing = true)
public class MyBatisAutoConfiguration {
// 类上的多个条件注解是 AND 关系:必须全部满足
}
自定义 OR 组合条件:
java
public class OnRedisOrCacheCondition extends AnyNestedCondition {
OnRedisOrCacheCondition() {
super(ConfigurationPhase.REGISTER_BEAN); // 在 Bean 注册阶段判断
}
@ConditionalOnClass(RedisConnectionFactory.class)
@ConditionalOnBean(RedisConnectionFactory.class)
static class RedisCondition {}
@ConditionalOnClass(CacheManager.class)
@ConditionalOnProperty(prefix = "spring.cache", name = "type", havingValue = "simple")
static class CacheCondition {}
}
java
@Conditional(OnRedisOrCacheCondition.class)
public class SessionAutoConfiguration {
// 满足 RedisCondition OR CacheCondition 任一即可激活
}
四、自动装配的执行顺序:@AutoConfigureOrder 与 @AutoConfigureAfter
4.1 配置类的加载顺序控制
自动配置类之间往往有依赖关系。例如:
DataSourceAutoConfiguration必须在HibernateJpaAutoConfiguration之前ServletWebServerFactoryAutoConfiguration必须在DispatcherServletAutoConfiguration之前
Spring Boot 提供了三种顺序控制机制:
| 注解 | 作用 | 示例 |
|---|---|---|
@AutoConfigureOrder |
全局排序值(Ordered 接口) | @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE) |
@AutoConfigureAfter |
在指定配置类之后加载 | @AutoConfigureAfter(DataSourceAutoConfiguration.class) |
@AutoConfigureBefore |
在指定配置类之前加载 | @AutoConfigureBefore(HibernateJpaAutoConfiguration.class) |
4.2 源码中的典型示例
java
@Configuration(proxyBeanMethods = false)
@AutoConfigureAfter(DataSourceAutoConfiguration.class) // 数据源配置完成后
@AutoConfigureBefore(HibernateJpaAutoConfiguration.class) // 在 JPA 之前
@ConditionalOnClass({ LocalContainerEntityManagerFactoryBean.class, EntityManager.class })
@ConditionalOnBean(DataSource.class) // 需要 DataSource Bean
@EnableConfigurationProperties(JpaProperties.class)
@Import({ HibernateJpaConfiguration.class, DataSourceInitializedPublisher.class })
public class HibernateJpaAutoConfiguration {
// 配置内容...
}
五、自定义 Starter 实战:从零到发布
5.1 Starter 的项目结构
一个标准的自定义 Starter 项目结构:
my-custom-spring-boot-starter/
├── pom.xml
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com/example/mystarter/
│ │ │ ├── auto/
│ │ │ │ ├── MyServiceAutoConfiguration.java
│ │ │ │ └── MyServiceProperties.java
│ │ │ ├── service/
│ │ │ │ ├── MyService.java
│ │ │ │ └── DefaultMyService.java
│ │ │ └── condition/
│ │ │ └── OnMyFeatureEnabledCondition.java
│ │ └── resources/
│ │ └── META-INF/
│ │ └── spring/
│ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports
│ └── test/
│ └── java/
│ └── com/example/mystarter/
│ └── MyServiceAutoConfigurationTest.java
5.2 核心文件实现
AutoConfiguration.imports(Spring Boot 3.x 格式):
com.example.mystarter.auto.MyServiceAutoConfiguration
属性配置类:
java
package com.example.mystarter.auto;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.bind.DefaultValue;
@ConfigurationProperties(prefix = "my.starter")
public class MyServiceProperties {
/**
* 是否启用该 Starter 的功能
*/
private boolean enabled = true;
/**
* 服务名称
*/
private String serviceName = "default-service";
/**
* 最大重试次数
*/
@DefaultValue("3")
private int maxRetries;
/**
* 超时时间(毫秒)
*/
@DefaultValue("5000")
private long timeoutMillis;
// Getters and Setters
public boolean isEnabled() { return enabled; }
public void setEnabled(boolean enabled) { this.enabled = enabled; }
public String getServiceName() { return serviceName; }
public void setServiceName(String serviceName) { this.serviceName = serviceName; }
public int getMaxRetries() { return maxRetries; }
public void setMaxRetries(int maxRetries) { this.maxRetries = maxRetries; }
public long getTimeoutMillis() { return timeoutMillis; }
public void setTimeoutMillis(long timeoutMillis) { this.timeoutMillis = timeoutMillis; }
}
服务接口与默认实现:
java
package com.example.mystarter.service;
public interface MyService {
String process(String input);
boolean healthCheck();
}
java
package com.example.mystarter.service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class DefaultMyService implements MyService {
private static final Logger log = LoggerFactory.getLogger(DefaultMyService.class);
private final String serviceName;
private final int maxRetries;
private final long timeoutMillis;
public DefaultMyService(String serviceName, int maxRetries, long timeoutMillis) {
this.serviceName = serviceName;
this.maxRetries = maxRetries;
this.timeoutMillis = timeoutMillis;
}
@Override
public String process(String input) {
log.info("[{}] Processing input with timeout={}ms, retries={}",
serviceName, timeoutMillis, maxRetries);
return String.format("[%s] Processed: %s", serviceName, input.toUpperCase());
}
@Override
public boolean healthCheck() {
return true;
}
}
自动配置类:
java
package com.example.mystarter.auto;
import com.example.mystarter.service.DefaultMyService;
import com.example.mystarter.service.MyService;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
/**
* 自定义 Starter 的自动配置类
*/
@AutoConfiguration
@ConditionalOnClass(MyService.class) // 类路径存在 MyService
@ConditionalOnProperty(prefix = "my.starter", name = "enabled",
havingValue = "true", matchIfMissing = true) // 默认启用
@EnableConfigurationProperties(MyServiceProperties.class)
public class MyServiceAutoConfiguration {
@Bean
@ConditionalOnMissingBean(MyService.class) // 用户未自定义时提供默认实现
public MyService myService(MyServiceProperties properties) {
return new DefaultMyService(
properties.getServiceName(),
properties.getMaxRetries(),
properties.getTimeoutMillis()
);
}
}
5.3 条件注解的深度应用:自定义 Condition
java
package com.example.mystarter.condition;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
/**
* 自定义条件:检查是否处于生产环境
*/
public class OnProductionEnvironmentCondition extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
String activeProfile = context.getEnvironment().getProperty("spring.profiles.active", "");
boolean isProd = activeProfile.contains("prod") ||
activeProfile.contains("production");
return isProd
? ConditionOutcome.match("Production environment detected")
: ConditionOutcome.noMatch("Not in production environment");
}
}
java
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnProductionEnvironmentCondition.class)
public @interface ConditionalOnProduction {
}
5.4 使用 Starter 的应用端配置
yaml
# application.yml
my:
starter:
enabled: true
service-name: payment-gateway
max-retries: 5
timeout-millis: 10000
spring:
profiles:
active: prod
java
package com.example.myapp;
import com.example.mystarter.service.MyService;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
@Component
public class AppRunner implements CommandLineRunner {
private final MyService myService;
public AppRunner(MyService myService) {
this.myService = myService;
}
@Override
public void run(String... args) {
String result = myService.process("hello world");
System.out.println(result); // 输出: [payment-gateway] Processed: HELLO WORLD
System.out.println("Health: " + myService.healthCheck());
}
}
5.5 测试自动配置
java
package com.example.mystarter;
import com.example.mystarter.auto.MyServiceAutoConfiguration;
import com.example.mystarter.service.MyService;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import static org.assertj.core.api.Assertions.assertThat;
class MyServiceAutoConfigurationTest {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(MyServiceAutoConfiguration.class));
@Test
void serviceBeanIsCreatedByDefault() {
this.contextRunner
.withPropertyValues("my.starter.service-name=test-service")
.run(context -> {
assertThat(context).hasSingleBean(MyService.class);
assertThat(context.getBean(MyService.class).process("test"))
.contains("test-service");
});
}
@Test
void serviceBeanIsNotCreatedWhenDisabled() {
this.contextRunner
.withPropertyValues("my.starter.enabled=false")
.run(context -> assertThat(context).doesNotHaveBean(MyService.class));
}
@Test
void userDefinedBeanOverridesDefault() {
this.contextRunner
.withBean(MyService.class, () -> input -> "Custom: " + input)
.run(context -> {
assertThat(context).hasSingleBean(MyService.class);
assertThat(context.getBean(MyService.class).process("test"))
.isEqualTo("Custom: test");
});
}
}
六、自动装配的调试与诊断
6.1 开启自动装配报告
在 application.yml 中添加:
yaml
debug: true
或启动时添加参数:
bash
java -jar myapp.jar --debug
控制台将输出详细的 Positive matches (已匹配)和 Negative matches(未匹配)报告:
============================
CONDITIONS EVALUATION REPORT
============================
Positive matches:
-----------------
AopAutoConfiguration matched:
- @ConditionalOnProperty (spring.aop.auto=true) matched (OnPropertyCondition)
DataSourceAutoConfiguration matched:
- @ConditionalOnClass (javax.sql.DataSource, ...) found required classes
Negative matches:
-----------------
ActiveMQAutoConfiguration:
Did not match:
- @ConditionalOnClass did not find required class 'jakarta.jms.ConnectionFactory'
6.2 使用 EnvironmentEndpoint 查看加载的配置
bash
curl http://localhost:8080/actuator/conditions
返回 JSON 格式的条件评估结果,便于程序化分析。
总结
Spring Boot 的自动装配机制是一个精心设计的分层决策系统:
- 入口层 :
@EnableAutoConfiguration→AutoConfigurationImportSelector→SpringFactoriesLoader - 发现层 :从
META-INF/spring.factories或META-INF/spring/*.imports读取候选配置 - 决策层 :
@Conditional注解家族执行条件匹配,过滤出实际需要加载的配置 - 顺序层 :
@AutoConfigureOrder/@AutoConfigureAfter/@AutoConfigureBefore控制依赖顺序 - 执行层:Spring 容器注册 Bean 定义,完成自动装配
关键要点回顾:
SpringFactoriesLoader是 SPI 扩展的核心,新格式AutoConfiguration.imports更清晰@ConditionalOnClass使用 ASM 扫描避免类加载副作用,是设计亮点@ConditionalOnMissingBean提供默认实现同时允许用户覆盖,是"约定优于配置"的精髓- 自定义 Starter 需遵循
AutoConfiguration→Properties→Condition三层结构 - 使用
ApplicationContextRunner进行自动配置测试,是 Spring Boot 2.0+ 推荐方式
建议 :在生产环境中使用
debug: true或 Actuator 的conditions端点排查自动装配问题。当多个 Starter 冲突时,优先通过@ConditionalOnProperty提供显式开关,而非依赖@Order调整优先级,这样更可控、更透明。