@ConditionalOnMissingBean 注解
@ConditionalOnMissingBean 注解详解
核心概念
@ConditionalOnMissingBean 是 Spring Boot 提供的条件注解,用于控制 Bean 的创建条件。
总结:如果没有就用我的,有了就用你的。
语法格式
@Bean
@ConditionalOnMissingBean(需要检查的类.class) // 注意:不需要引号
public BeanType beanName() {
return new BeanType();
}
主要属性
| 属性 | 说明 | 示例 |
|---|---|---|
value |
按类型指定要检查的 Bean 类 | @ConditionalOnMissingBean(DataSource.class) |
name |
按名称指定要检查的 Bean | @ConditionalOnMissingBean(name = "myService") |
search |
限制搜索的 ApplicationContext 层级 | @ConditionalOnMissingBean(search = SearchStrategy.CURRENT) |
工作原理
┌─────────────────────────────────────────────────────────────┐
│ @ConditionalOnMissingBean │
├─────────────────────────────────────────────────────────────┤
│ │
│ 检查容器中是否存在目标 Bean │
│ │
│ ┌─────────────┐ 存在? ┌──────────────┐ │
│ │ 目标 Bean │ ─────────> │ 不创建当前Bean│ │
│ └─────────────┘ 是 └──────────────┘ │
│ │
│ ┌─────────────┐ 不存在 ┌──────────────┐ │
│ │ 目标 Bean │ ─────────> │ 创建当前 Bean│ │
│ └─────────────┘ └──────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
使用场景
- 提供默认实现:当用户没有自定义 Bean 时提供默认值
- 避免 Bean 冲突:防止创建重复的 Bean
- 可扩展的自动配置:允许用户覆盖默认配置
- 按需加载:只在需要时才创建 Bean
代码示例
1. 基本用法(按类型检查)
@Configuration
public class DefaultDataSourceAutoConfiguration {
@Bean
@ConditionalOnMissingBean(DataSource.class)
public DataSource defaultDataSource() {
System.out.println("创建默认数据源");
return new HikariDataSource();
}
}
2. 按名称检查
@Bean
@ConditionalOnMissingBean(name = "myCustomService")
public MyService myService() {
return new MyService();
}
3. 默认返回类型检查
@Bean
@ConditionalOnMissingBean // 等价于 @ConditionalOnMissingBean(CacheService.class)
public CacheService cacheService(CacheManager cacheManager) {
return new CacheService(cacheManager);
}
4. 完整示例:用户可覆盖的自动配置
@Configuration
@EnableConfigurationProperties(CacheProperties.class)
public class CacheAutoConfiguration {
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(prefix = "app.cache", name = "enabled", havingValue = "true")
public CacheManager cacheManager(CacheProperties properties) {
RedisCacheManager manager = new RedisCacheManager();
manager.setHost(properties.getHost());
manager.setPort(properties.getPort());
manager.setTtl(properties.getTtl());
return manager;
}
}
不加注解会出现什么问题?
问题:Bean 冲突导致启动失败
不加注解的情况:
// 框架自动配置
@Configuration
public class DefaultConfig {
@Bean // ❌ 没有加 @ConditionalOnMissingBean
public DataSource dataSource() {
return new HikariDataSource();
}
}
// 用户自定义配置
@Configuration
public class MyConfig {
@Bean
public DataSource dataSource() {
return new MysqlDataSource();
}
}
运行结果:报错!
***************************
APPLICATION FAILED TO START
***************************
The bean 'dataSource', defined in DefaultConfig,
could not be registered. A bean with that name has already been defined
and overriding is disabled.
加注解的情况
// 框架自动配置
@Configuration
public class DefaultConfig {
@Bean
@ConditionalOnMissingBean(DataSource.class) // ✅ 加上这个注解
public DataSource dataSource() {
System.out.println("创建默认数据源");
return new HikariDataSource();
}
}
// 用户自定义配置
@Configuration
public class MyConfig {
@Bean
public DataSource dataSource() {
System.out.println("创建自定义数据源");
return new MysqlDataSource();
}
}
运行结果:
- 用户定义了 DataSource → 使用用户的,默认的不会创建
- 用户没定义 DataSource → 使用默认的
// 用户定义了 DataSource,控制台输出:
创建自定义数据源
// 用户没定义 DataSource,控制台输出:
创建默认数据源
对比总结
| 情况 | 不加注解 | 加注解 |
|---|---|---|
| 用户定义了 Bean | ❌ Bean 冲突,启动失败 | ✅ 使用用户的 Bean |
| 用户没定义 Bean | ✅ 创建默认 Bean | ✅ 创建默认 Bean |
类级别 vs 方法级别
// 方法级别:只阻止该Bean的注册
@Configuration
public class MyConfig {
@Bean
@ConditionalOnMissingBean
public MyBean myBean() { ... } // 只影响这个Bean
@Bean
public OtherBean otherBean() { ... } // 不受影响
}
// 类级别:阻止整个配置类作为Bean注册
@Configuration
@ConditionalOnMissingBean
public class MyConfig {
@Bean
public MyBean myBean() { ... } // 条件不匹配时整个类都不会注册
@Bean
public OtherBean otherBean() { ... }
}
与 @ConditionalOnBean 的对比
@ConditionalOnBean(DataSource.class) // 容器中存在DataSource时创建
@ConditionalOnMissingBean(DataSource.class) // 容器中不存在DataSource时创建
这两个注解互为相反的条件,常用于实现"可选依赖"或"默认回退"的配置模式。
核心要点
- 使用场景:方便被替换 - 完全正确
- 效果:如果用户自己实现了这个类就不会注入了 - 完全正确
- 语法:
@ConditionalOnMissingBean(类.class)- 不需要引号