一句话定位:
@ConditionalOnBean是 Spring Boot 自动配置的容器依赖链探测器,它确保只有当 Spring 容器中已经存在指定 Bean 时,当前配置类或方法才生效——用于在自动配置类之间建立"前置依赖"关系,保证配置加载的有序性和正确性。
定义与作用
@ConditionalOnBean 是 Spring Boot 条件注解家族中负责容器内依赖检查的核心成员。它的判断逻辑是:检查当前 Spring 容器中是否已经注册了指定类型或名称的 Bean。
- 如果已存在 → 条件满足 → 当前配置生效
- 如果不存在 → 条件不满足 → 当前配置被跳过
与 @ConditionalOnClass 的本质区别
| 对比维度 | @ConditionalOnClass | @ConditionalOnBean |
|---|---|---|
| 检查对象 | 类路径中的类文件 | 容器中的 Bean 实例/定义 |
| 检查时机 | 类加载阶段(早期) | BeanDefinition 注册阶段(稍晚) |
| 典型用途 | 判断依赖是否引入 | 判断前置配置是否生效 |
| 与容器关系 | 不依赖容器状态 | 完全依赖容器状态 |
在自动配置中的典型角色
自动配置类之间的依赖链
↓
DataSourceAutoConfiguration → 注册 DataSource Bean
↓
JdbcTemplateAutoConfiguration 需要 @ConditionalOnBean(DataSource.class)
→ "只有当 DataSource 存在时,我才注册 JdbcTemplate"
↓
MyBatisAutoConfiguration 需要 @ConditionalOnBean(DataSource.class)
→ "只有当 DataSource 存在时,我才注册 SqlSessionFactory"
关键应用场景:
JdbcTemplateAutoConfiguration依赖DataSourceAutoConfiguration的结果RedisCacheConfiguration依赖RedisAutoConfiguration创建的RedisConnectionFactory- 自定义自动配置模块依赖官方自动配置模块创建的 Bean
适用位置与常用属性
适用位置
@ConditionalOnBean 可以标注在:
- 类级别:控制整个配置类是否生效
- @Bean 方法级别:控制单个 Bean 是否注册
// 类级别:整个配置类依赖前置 Bean
@Configuration(proxyBeanMethods = false)
@ConditionalOnBean(DataSource.class)
public class JdbcTemplateAutoConfiguration { ... }
// 方法级别:单个 Bean 依赖前置 Bean
@Configuration
public class AdvancedConfig {
@Bean
@ConditionalOnBean(name = "dataSource")
public TransactionTemplate transactionTemplate(DataSource dataSource) {
return new TransactionTemplate();
}
}
常用属性
| 属性 | 类型 | 说明 |
|---|---|---|
value | Class<?>[] | 指定必须存在的 Bean 类型(类型安全) |
name | String[] | 指定必须存在的 Bean 名称(按名称匹配) |
type | String[] | 指定必须存在的 Bean 类型全限定名(字符串形式) |
search | SearchStrategy | 搜索策略:CURRENT、ANCESTORS、ALL |
annotation | Class<? extends Annotation>[] | 指定必须存在带有该注解的 Bean |
重要:
@ConditionalOnBean和@ConditionalOnMissingBean的属性完全对称,但判断逻辑相反。前者是"存在才生效",后者是"不存在才生效"。
核心原理
容器内 Bean 存在性检查
自动配置类之间的依赖链
关键理解:@ConditionalOnBean 在自动配置类之间构建了一条隐式依赖链。JdbcTemplateAutoConfiguration 不需要知道 DataSourceAutoConfiguration 的存在,它只需要知道"容器中是否有 DataSource Bean"。这种解耦设计使得各个自动配置类可以独立开发和维护,同时保证正确的加载顺序。
完整示例
场景简述
飞翔科技(广州)的学生成绩管理系统 com.feixiang.student 使用 Spring Boot 2.7.18。架构师白歌要求小崔开发一个数据库健康检查模块:只有当数据源已自动配置完成时,才注册一个 DatabaseHealthIndicator Bean;如果没有数据源(比如纯内存缓存模式),则不注册健康检查器,避免启动报错。
小崔需要利用 @ConditionalOnBean 来实现这个"依赖前置"逻辑。
操作前:硬编码依赖导致启动失败
// 操作前:错误示范,未使用条件注解
package com.feixiang.student.health;
import org.springframework.stereotype.Component;
import javax.sql.DataSource;
@Component
public class DatabaseHealthIndicator {
private final DataSource dataSource;
// 问题:如果 DataSource 不存在,这个构造器参数无法注入
public DatabaseHealthIndicator(DataSource dataSource) {
this.dataSource = dataSource;
}
public String check() {
return "检查 student_db 连接状态";
}
}
后果:当项目未引入 spring-boot-starter-jdbc(或数据源配置不完整)时,容器中不存在 DataSource Bean。Spring 尝试注入 DatabaseHealthIndicator 的构造器参数时抛出 NoSuchBeanDefinitionException:
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException:
No qualifying bean of type 'javax.sql.DataSource' available:
expected at least 1 bean which qualifies as autowire candidate
小崔被白歌叫去排查:"为什么去掉数据库依赖后项目起不来了?健康检查应该是可选的!"
使用该注解的完整代码
小崔改用 @ConditionalOnBean 后,健康检查模块变为:
步骤 1:创建条件化的健康检查配置类
package com.feixiang.student.config;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
/**
* 数据库健康检查自动配置类
* 只有当容器中已存在 DataSource Bean 时,本配置才生效
*
* @author 小崔
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnBean(DataSource.class) // ← 关键:前置依赖检查
public class HealthCheckAutoConfiguration {
@Bean
public DatabaseHealthIndicator databaseHealthIndicator(DataSource dataSource) {
return new DatabaseHealthIndicator(dataSource);
}
}
步骤 2:健康检查器实现
package com.feixiang.student.health;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
public class DatabaseHealthIndicator {
private final DataSource dataSource;
public DatabaseHealthIndicator(DataSource dataSource) {
this.dataSource = dataSource;
}
public String check() {
try (Connection conn = dataSource.getConnection()) {
if (conn.isValid(5)) {
return "student_db 连接正常";
} else {
return "student_db 连接异常";
}
} catch (SQLException e) {
return "student_db 连接失败: " + e.getMessage();
}
}
}
步骤 3:在 application.yml 中配置数据源
# application.yml(有数据源场景)
spring:
datasource:
url: jdbc:mysql://localhost:3306/student_db
username: root
password: secret
driver-class-name: com.mysql.cj.jdbc.Driver
# 没有 feixiang.health 相关配置,因为 @ConditionalOnBean 是自动检测的
步骤 4:业务代码中安全注入
package com.feixiang.student.service;
import com.feixiang.student.health.DatabaseHealthIndicator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class StudentService {
private final DatabaseHealthIndicator healthIndicator;
@Autowired
public StudentService(@Autowired(required = false) DatabaseHealthIndicator healthIndicator) {
this.healthIndicator = healthIndicator;
}
public String queryWithHealthCheck(Long studentId) {
if (healthIndicator != null) {
String dbStatus = healthIndicator.check();
System.out.println("数据库状态: " + dbStatus);
}
return "查询学生 " + studentId + " 的成绩";
}
}
操作后运行结果及分析
场景 A:配置完整数据源(健康检查生效)
2024-05-20 16:00:15.123 INFO 12345 --- [main] o.s.b.a.h.HikariDataSourceConfiguration :
HikariPool-1 - Start completed
2024-05-20 16:00:15.456 INFO 12345 --- [main] c.f.s.c.HealthCheckAutoConfiguration :
HealthCheckAutoConfiguration matched: @ConditionalOnBean found DataSource bean
2024-05-20 16:00:15.678 INFO 12345 --- [main] c.f.s.c.HealthCheckAutoConfiguration :
DatabaseHealthIndicator bean registered
2024-05-20 16:00:15.789 INFO 12345 --- [main] c.f.s.StudentManagementApplication :
Started StudentManagementApplication in 2.123 seconds
场景 B:无数据源配置(健康检查跳过)
# application.yml(无数据源场景)
# 不配置 spring.datasource
2024-05-20 16:05:33.123 INFO 12345 --- [main] o.s.b.a.c.AutoConfigurationReport :
HealthCheckAutoConfiguration:
Did not match:
- @ConditionalOnBean (types: javax.sql.DataSource; SearchStrategy: all)
did not find any beans (OnBeanCondition)
2024-05-20 16:05:33.456 INFO 12345 --- [main] c.f.s.StudentManagementApplication :
Started StudentManagementApplication in 1.456 seconds
分析:
- 有数据源时:
DataSourceAutoConfiguration检测到HikariDataSource在类路径且配置完整,注册DataSourceBean。随后HealthCheckAutoConfiguration的@ConditionalOnBean(DataSource.class)检查通过,注册DatabaseHealthIndicator。 - 无数据源时:
DataSourceAutoConfiguration因缺少配置或依赖被跳过,容器中没有DataSource。HealthCheckAutoConfiguration的@ConditionalOnBean检查失败,被静默跳过,不会报错。 - 依赖链解耦:
HealthCheckAutoConfiguration不需要直接依赖DataSourceAutoConfiguration类,只需要检查容器状态。这符合 Spring Boot 自动配置的"隐式依赖"设计哲学。
易错场景与面试考点
易错场景一:与 @ConditionalOnClass 混用导致误判
// 错误示范:只用 @ConditionalOnClass,忽略了容器内依赖
package com.feixiang.student.config;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
@Configuration
@ConditionalOnClass(DataSource.class) // ← 只检查了类路径,没检查容器
public class BadHealthCheckConfig {
@Bean
public DatabaseHealthIndicator databaseHealthIndicator(DataSource dataSource) {
return new DatabaseHealthIndicator(dataSource);
}
}
后果:spring-boot-starter-jdbc 在类路径中(DataSource.class 存在),但用户没有在 application.yml 中配置 spring.datasource.url。@ConditionalOnClass 通过了,但容器中实际上没有可用的 DataSource Bean(DataSourceAutoConfiguration 因缺少配置被 @ConditionalOnProperty 或内部条件跳过)。结果 BadHealthCheckConfig 被加载,尝试创建 DatabaseHealthIndicator 时注入失败,抛出 NoSuchBeanDefinitionException。
正确做法:如果当前配置类依赖某个 Bean 实例(而非仅仅依赖类路径存在),应该使用 @ConditionalOnBean:
@Configuration(proxyBeanMethods = false)
@ConditionalOnBean(DataSource.class) // 正确:检查容器内是否有 DataSource Bean
public class HealthCheckAutoConfiguration {
// ...
}
易错场景二:在普通业务组件上使用 @ConditionalOnBean
// 错误示范:在 @Service 上使用条件注解
package com.feixiang.student.service;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.stereotype.Service;
import javax.sql.DataSource;
@Service
@ConditionalOnBean(DataSource.class) // ← 不推荐在业务层使用
public class StudentService {
// ...
}
后果:虽然技术上可以工作,但 @ConditionalOnBean 设计初衷是用于自动配置类(@Configuration)。在 @Service 上使用会导致业务代码与基础设施耦合——如果 DataSource 不存在,StudentService 整个服务都不存在,业务层逻辑与数据层配置强绑定。更严重的是,如果其他组件 @Autowired StudentService,当 DataSource 不存在时会抛出 NoSuchBeanDefinitionException。
正确做法:在业务层使用 @Autowired(required = false) 或 Optional 注入,让业务代码具备可选依赖的处理能力;条件注解只用于自动配置层:
@Service
public class StudentService {
private final DataSource dataSource;
@Autowired
public StudentService(@Autowired(required = false) DataSource dataSource) {
this.dataSource = dataSource;
}
}
面试考点
Q:@ConditionalOnBean 和 @ConditionalOnClass 有什么区别?
@ConditionalOnBean检查的是容器内是否已经注册了某个 Bean(动态,依赖容器状态);@ConditionalOnClass检查的是类路径中是否存在某个类文件(静态,与容器状态无关)。前者用于自动配置类之间的依赖链构建,后者用于判断是否引入了某个 Starter 依赖。两者可以组合使用:@ConditionalOnClass确保依赖存在,@ConditionalOnBean确保前置配置已生效。
Q:@ConditionalOnBean 的判断发生在什么时机?
发生在 BeanDefinition 注册阶段,即
AutoConfigurationImportSelector处理候选配置类时。由于自动配置类通过DeferredImportSelector延迟处理,此时用户自定义 Bean 和普通配置类已经注册完毕。因此@ConditionalOnBean能够准确判断"容器中是否已有某 Bean"。
Q:如果 A 自动配置类 @ConditionalOnBean(B.class),但 B 也是自动配置类,会不会出现顺序问题?
不会。Spring Boot 的自动配置类都通过
DeferredImportSelector统一处理,且处理时会按拓扑顺序排列(考虑@AutoConfigureBefore/@AutoConfigureAfter注解)。如果 B 的自动配置在 A 之前执行,B 的 Bean 会先注册到容器,A 的@ConditionalOnBean就能正确判断。如果 A 和 B 没有明确的顺序关系,Spring Boot 的自动配置排序机制会确保它们按正确的依赖顺序处理。
Q:@ConditionalOnBean 和 @ConditionalOnMissingBean 能否同时标注在同一个类上?
技术上可以,但逻辑上通常没有意义。
@ConditionalOnBean(X.class)要求"存在 X",@ConditionalOnMissingBean(X.class)要求"不存在 X"——两者互斥,同时标注会导致条件永远无法同时满足。正确的做法是:用@ConditionalOnMissingBean创建默认实现,用@ConditionalOnBean在后续配置中依赖该实现。
Q:@ConditionalOnBean 的 search 属性在 Spring Boot 单应用中有意义吗?
在典型的 Spring Boot 单
ApplicationContext应用中,search的默认值ALL已经足够。search主要在父子容器场景中有用,例如 Spring MVC 的传统父子容器结构:子上下文(DispatcherServlet 上下文)中的@ConditionalOnBean(search = SearchStrategy.CURRENT)只检查子容器,不检查父容器(Root WebApplicationContext)。Spring Boot 的嵌入式容器模式下通常只有一个上下文,因此search差异不明显。