Spring Boot @ConditionalOnBean 注解:Bean 注册的“条件管家”,这样用才够优雅!
@ConditionalOnBean 是 Spring Boot 中用于条件化创建 Bean 的核心注解。本文通过"游戏机与手柄"的类比,介绍了它的三大核心作用:按需加载 Bean、避免依赖冲突和支持模块化设计。文章详细讲解了基础用法(3步实现数据源依赖)和5个进阶属性:value/type(按类型匹配)、name(按名称匹配)、annotation(按注解匹配)、search
在 Spring Boot 开发中,你是否遇到过这样的场景:想让某个 Bean 只在特定依赖 Bean 存在时才创建?比如只有数据源 Bean 存在,数据库服务 Bean 才生效;或者 Redis 模板 Bean 存在,缓存管理器才加载?这时,@ConditionalOnBean 注解就能派上大用场——它就像一位“条件管家”,精准控制 Bean 的注册时机,让你的代码更灵活、更符合模块化设计理念。今天这篇教程,我们就从基础到实战,彻底掌握它的用法!
ConditionalOnBean 源码
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnBeanCondition.class)
public @interface ConditionalOnBean {
// bean 类的类型
Class<?>[] value() default {};
// bean 类的类型名称(类路径,例如:String 类是 "java.lang.String")
String[] type() default {};
// bean 上拥有指定注解
Class<? extends Annotation>[] annotation() default {};
// bean 的名称
String[] name() default {};
// 容器层级
// ALL:所有容器
// ANCESTORS:所有祖先容器,但不包含当前容器
// CURRENT:仅当前容器
SearchStrategy search() default SearchStrategy.ALL;
// 指定泛型容器类型
Class<?>[] parameterizedContainer() default {};
}
一、@ConditionalOnBean 作用是?
在 Spring Boot 的“条件注解家族”中,@ConditionalOnBean 是最核心的成员之一,它的核心作用一句话就能说清:只有当 Spring 容器中已经存在指定的 Bean 时,被该注解标注的 Bean 才会被注册到容器中。
举个生活中的例子:就像买游戏机(依赖 Bean)和游戏手柄(当前 Bean)的关系——只有先买了游戏机(容器中有游戏机 Bean),商家才会给你配手柄(注册手柄 Bean);如果没买游戏机,手柄再好用也不会给你(不注册手柄 Bean)。
它能帮我们解决三大核心问题:
-
按需加载 Bean:避免无用 Bean 占用资源,比如只有数据源存在时才创建数据库服务。
-
避免依赖冲突:确保依赖 Bean 存在后再创建当前 Bean,防止因缺少依赖导致启动失败。
-
支持模块化设计:让功能模块“按需启用”,比如只有引入 Redis 依赖且存在 Redis 相关 Bean 时,缓存模块才生效。
二、基本用法
@ConditionalOnBean 的基础用法非常简单,我们通过一个“数据源依赖”的例子,3 步就能上手。
第 1 步:定义依赖 Bean(比如 DataSource)
首先创建一个“依赖 Bean”——这里用模拟的数据源 FakeDataSource 为例:
import javax.sql.DataSource;
import java.sql.*;
// 模拟数据源实现
class FakeDataSource implements DataSource {
@Override
public Connection getConnection() throws SQLException {
return null; // 实际项目需实现真实逻辑
}
// 其他接口方法空实现,此处省略
}
第 2 步:用 @ConditionalOnBean 标注目标 Bean
在配置类中,先注册 DataSource Bean,再用 @ConditionalOnBean(DataSource.class) 标注 DatabaseService——表示“只有 DataSource 存在时,才创建 DatabaseService”:
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class DatabaseConfig {
// 注册依赖 Bean:数据源
@Bean
public DataSource dataSource() {
return new FakeDataSource();
}
// 条件注册:只有 DataSource 存在,才创建 DatabaseService
@Bean
@ConditionalOnBean(DataSource.class)
public DatabaseService databaseService() {
return new DatabaseService();
}
}
// 目标服务类
class DatabaseService {
public DatabaseService() {
System.out.println("✅ DatabaseService 已创建(数据源存在)");
}
}
第 3 步:验证效果
-
正常情况:运行项目,控制台会输出
✅ DatabaseService 已创建(因为 DataSource 存在),说明两个 Bean 都成功注册。 -
依赖缺失情况:注释掉
dataSource()方法(即不注册 DataSource),再运行项目——此时控制台不会输出上述日志,因为DatabaseService因缺少依赖而不被注册。
三、进阶:5 个核心属性,满足复杂场景
@ConditionalOnBean 提供了 5 个关键属性,让我们能更精细地控制“条件”。掌握这些属性,就能应对 90% 以上的实战需求。
1. value / type:按 Bean 类型匹配
这两个属性都用于“根据 Bean 的类型判断”,但用法略有区别:
-
value:直接指定 Class 类型,编译时会检查类是否存在,更安全。
-
type:用字符串指定类的全限定名,适合“类可能不存在”的场景(比如可选依赖)。
示例 1:用 value 指定类型
// 存在 DataSource 类型 Bean 时,注册 MyRepository
@Bean
@ConditionalOnBean(value = DataSource.class)
public MyRepository myRepository() {
return new MyRepository();
}
示例 2:用 type 指定类型
// 效果同上,用全限定名字符串指定类型
@Bean
@ConditionalOnBean(type = "javax.sql.DataSource")
public MyRepository myRepository() {
return new MyRepository();
}
2. name:按 Bean 名称匹配
如果需要“精确匹配某个名称的 Bean”,就用 name 属性——比如只当名为 redisTemplate 的 Bean 存在时,才创建缓存管理器。
示例:按名称匹配
// 存在名为 "redisTemplate" 的 Bean 时,注册缓存管理器
@Bean
@ConditionalOnBean(name = "redisTemplate")
public RedisCacheManager redisCacheManager() {
return RedisCacheManager.builder().build();
}
3. annotation:按 Bean 上的注解匹配
这个属性很实用——它判断“容器中是否存在被指定注解标注的 Bean”。比如只当存在 @Repository 注解的 Bean 时,才创建数据同步服务。
示例:按注解匹配
// 存在 @Repository 注解的 Bean 时,注册 DataSyncService
@Bean
@ConditionalOnBean(annotation = Repository.class)
public DataSyncService dataSyncService() {
return new DataSyncService();
}
4. search:按 Bean 的类型(泛型)
Spring 容器可能存在“父子上下文”(比如 Web 应用中的 ServletContext 和 RootContext),search 属性用于指定“在哪些上下文中搜索 Bean”:
-
SearchStrategy.CURRENT(默认):只在当前上下文搜索。
-
SearchStrategy.ANCESTORS:只在祖先类的上下文搜索,不包含当前上下文。
-
SearchStrategy.ALL:在所有父子上下文中搜索。
示例:跨上下文搜索
// 在所有上下文中搜索 DataSource,存在即注册 MyService
@Bean
@ConditionalOnBean(
value = DataSource.class,
search = SearchStrategy.ALL
)
public MyService myService() {
return new MyService();
}
5. parameterizedContainer:控制 Bean 的搜索范围
存在某个 bean 为 A ,A 的类型和 parameterizedContainer 指定的泛型类的类型一致(可以是它的实现类或继承子类),且 A 的泛型参数是 @ConditionalOnBean 注解的 bean 的类型。
示例:按泛型匹配
// 在所有上下文中搜索 DataSource,存在即注册 MyService
@Bean
public Set<Apple> set1() {
return new HashSet<>();
}
@Bean
public Set<Integer> set2() {
return new HashSet<>();
}
@Bean
@ConditionalOnBean(parameterizedContainer = Set.class)
public Apple apple() {
return new Apple();
}
四、实战:3 个典型场景,直接复用
光懂理论不够,我们结合真实开发场景,看看 @ConditionalOnBean 具体怎么用。
场景 1:按需加载数据库相关组件
项目中可能同时支持“关系型数据库”和“NoSQL 数据库”,但希望“只有当对应数据源存在时,才加载相关服务”。比如:
@Configuration
public class DbServiceConfig {
// 关系型数据库服务:只有 DataSource 存在时才加载
@Bean
@ConditionalOnBean(DataSource.class)
public JdbcDataService jdbcDataService() {
return new JdbcDataService();
}
// MongoDB 服务:只有 MongoTemplate 存在时才加载
@Bean
@ConditionalOnBean(MongoTemplate.class)
public MongoDataService mongoDataService() {
return new MongoDataService();
}
}
场景 2:配合自动配置生效
Spring Boot 的自动配置大量使用 @ConditionalOnBean。比如 Spring MVC 的自动配置中,只有当 DispatcherServlet 存在时,才加载 MVC 相关的拦截器、视图解析器等:
// Spring Boot 源码中的类似逻辑
@Configuration
@ConditionalOnBean(DispatcherServlet.class) // 依赖 DispatcherServlet
public class MvcAutoConfiguration {
// 注册 MVC 拦截器
@Bean
public MvcInterceptor mvcInterceptor() {
return new MvcInterceptor();
}
// 注册视图解析器
@Bean
public ViewResolver viewResolver() {
return new InternalResourceViewResolver();
}
}
场景 3:可选依赖的组件加载
如果项目中有“可选依赖”(比如可选 Redis 缓存),可以用 @ConditionalOnBean 控制“只有依赖的 Bean 存在时,才加载可选功能”。比如:
@Configuration
public class CacheAutoConfig {
// 只有当 redisTemplate 存在时,才加载 Redis 缓存管理器
@Bean
@ConditionalOnBean(name = "redisTemplate")
public CacheManager redisCacheManager(RedisTemplate redisTemplate) {
return RedisCacheManager.builder(redisTemplate.getConnectionFactory()).build();
}
// 只有当 caffeineCache 存在时,才加载 Caffeine 缓存管理器(备选方案)
@Bean
@ConditionalOnBean(name = "caffeineCache")
public CacheManager caffeineCacheManager() {
return CaffeineCacheManager();
}
}
五、避坑:和 @ConditionalOnMissingBean 的区别
很多人会把 @ConditionalOnBean 和 @ConditionalOnMissingBean 搞混,其实两者是“相反逻辑”,用错了会导致严重问题。我们用一张表清晰对比:
| 注解 | 核心逻辑 | 典型场景 |
|---|---|---|
@ConditionalOnBean |
存在指定 Bean 时,才注册当前 Bean | 依赖某个 Bean 才能生效(如数据源→数据库服务) |
@ConditionalOnMissingBean |
不存在指定 Bean 时,才注册当前 Bean | 提供默认 Bean(如没有自定义数据源时,注册默认内存数据源) |
| 示例:@ConditionalOnMissingBean 的用法 |
// 当容器中没有 DataSource 时,才注册默认的内存数据源(H2)
@Bean
@ConditionalOnMissingBean(DataSource.class)
public DataSource defaultDataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.build();
}
六、总结:一张图记住核心知识点
最后,我们用一张图总结 @ConditionalOnBean 的核心内容,帮你快速回顾:
@ConditionalOnBean 核心总结
├─ 作用:存在指定 Bean 时,才注册当前 Bean
├─ 5 个核心属性
│ ├─ value/type:按 Bean 类型匹配(value 用 Class,type 用字符串)
│ ├─ name:按 Bean 名称匹配
│ ├─ annotation:按 Bean 上的注解匹配
│ ├─ search:控制搜索范围(CURRENT/ANCESTORS/ALL)
│ └─ parameterizedContainer:指定泛型容器类型
├─ 3 个典型场景
│ ├─ 按需加载数据库组件
│ ├─ 配合自动配置生效
│ └─ 可选依赖的组件加载
└─ 关键区别:与 @ConditionalOnMissingBean 相反逻辑
最后一个小问题:你在项目中用过 @ConditionalOnBean 吗?
比如是否用它控制过数据源、缓存或消息队列相关的 Bean 加载?欢迎在评论区分享你的实战场景,我们一起交流进阶技巧!
更多推荐


所有评论(0)