一句话定位:
@ConditionalOnMissingBean是 Spring Boot 自动配置的用户配置优先守卫,它确保只有当容器中尚不存在某类型的 Bean 时,自动配置才创建默认实现;一旦用户自定义了同类 Bean,自动配置便优雅退让,实现"用户覆盖默认"的约定。
定义与作用
@ConditionalOnMissingBean 是 Spring Boot 条件注解家族中最能体现**"约定优于配置,配置优于硬编码"** 哲学的注解。它的判断逻辑是:检查当前 Spring 容器中是否已经存在指定类型或名称的 Bean。
- 如果不存在(Missing)→ 条件满足 → 自动配置创建默认 Bean
- 如果已存在 → 条件不满足 → 自动配置跳过,用户自定义 Bean 生效
在自动配置中的核心角色
自动配置类中的典型条件链
↓
@ConditionalOnClass → 类路径有依赖吗?
↓ 是
@ConditionalOnProperty → 配置开关打开了吗?
↓ 是
@ConditionalOnMissingBean → 用户已经自定义了吗?
↓ 否 → 创建默认 Bean
↓ 是 → 跳过,用户 Bean 优先
典型应用场景:
DataSourceAutoConfiguration内部使用@ConditionalOnMissingBean(DataSource.class),允许用户通过自定义DataSourceBean 覆盖默认的 HikariCP 配置WebMvcAutoConfiguration使用@ConditionalOnMissingBean(WebMvcConfigurationSupport.class),允许用户通过继承WebMvcConfigurationSupport完全接管 MVC 配置
适用位置与常用属性
适用位置
@ConditionalOnMissingBean 可以标注在:
- 类级别:控制整个配置类是否生效
- @Bean 方法级别:控制单个默认 Bean 是否注册
// 类级别:整个配置类受控
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(DataSource.class)
public class PooledDataSourceConfiguration { ... }
// 方法级别:单个默认 Bean 受控
@Configuration
public class CustomConfig {
@Bean
@ConditionalOnMissingBean(name = "studentDataSource")
public DataSource defaultDataSource() { ... }
}
常用属性
| 属性 | 类型 | 说明 |
|---|---|---|
value | Class<?>[] | 指定必须不存在的 Bean 类型(类型安全) |
name | String[] | 指定必须不存在的 Bean 名称(按名称匹配) |
type | String[] | 指定必须不存在的 Bean 类型全限定名(字符串形式) |
search | SearchStrategy | 搜索策略:CURRENT(当前上下文)、ANCESTORS(父上下文)、ALL(全部) |
annotation | Class<? extends Annotation>[] | 指定必须不存在带有该注解的 Bean |
重要:
value和type按类型匹配,name按名称匹配。如果同时指定多个属性,它们是**"与"关系**(AND),即所有条件都必须满足。
核心原理
容器存在性检查机制
为什么自动配置能"退让"?延迟导入机制
关键理解:AutoConfigurationImportSelector 实现 DeferredImportSelector,在所有用户配置和组件扫描完成后才执行。因此当 @ConditionalOnMissingBean 检查时,容器中已经包含了用户自定义的 Bean。如果用户定义了 DataSource,自动配置类就"看见"了这个 Bean,从而选择退让。
完整示例
场景简述
飞翔科技(广州)的学生成绩管理系统 com.feixiang.student 使用 Spring Boot 2.7.18 默认的 HikariCP 数据源。架构师白歌提出:"大多数项目用 HikariCP 就行,但如果有特殊需求(如连接 Oracle 需要特定参数),允许小崔自定义 DataSource Bean 覆盖默认配置。"
小崔需要验证:当他自定义 DataSource 时,Spring Boot 的自动配置是否会自动退让。
操作前:用户配置与自动配置冲突
小崔没有使用 Spring Boot,而是手动配置数据源,但不小心在两个 @Configuration 类中都定义了 DataSource:
// 操作前:错误示范(传统 Spring 方式)
package com.feixiang.student.config;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
@Configuration
public class DataSourceConfig {
@Bean
public DataSource dataSource() {
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://localhost:3306/student_db");
config.setUsername("root");
config.setPassword("secret");
return new HikariDataSource(config);
}
}
// 另一个配置类也定义了 DataSource!
package com.feixiang.student.config;
import org.apache.commons.dbcp2.BasicDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
@Configuration
public class LegacyDataSourceConfig {
@Bean
public DataSource legacyDataSource() {
BasicDataSource ds = new BasicDataSource();
ds.setUrl("jdbc:mysql://legacy-db:3306/student_db");
return ds;
}
}
后果:Spring 容器中出现两个 DataSource 类型的 Bean。当某个组件 @Autowired DataSource dataSource 时,Spring 抛出 NoUniqueBeanDefinitionException:
Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException:
No qualifying bean of type 'javax.sql.DataSource' available:
expected single matching bean but found 2: dataSource, legacyDataSource
小崔花了半天时间排查到底是哪里重复定义了数据源。
使用该注解的完整代码
小崔改用 Spring Boot 的自动配置机制后,启动类保持标准:
package com.feixiang.student;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class StudentManagementApplication {
public static void main(String[] args) {
SpringApplication.run(StudentManagementApplication.class, args);
}
}
默认情况(不自定义):DataSourceAutoConfiguration 通过 @ConditionalOnMissingBean 检查,发现容器中没有 DataSource Bean,于是创建默认的 HikariCP 数据源。
自定义情况(覆盖默认):当小崔需要特殊配置时,只需定义自己的 DataSource Bean:
package com.feixiang.student.config;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import javax.sql.DataSource;
/**
* 自定义数据源配置
* 覆盖 Spring Boot 默认的 HikariCP 配置
*
* @author 小崔
*/
@Configuration(proxyBeanMethods = false)
public class CustomDataSourceConfig {
@Bean
@Primary
public DataSource dataSource() {
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://localhost:3306/student_db?useSSL=false&serverTimezone=Asia/Shanghai");
config.setUsername("root");
config.setPassword("secret");
config.setMaximumPoolSize(20);
config.setMinimumIdle(5);
config.setConnectionTimeout(30000);
return new HikariDataSource(config);
}
}
注意:这里不需要排除
DataSourceAutoConfiguration,因为 Spring Boot 的DataSourceAutoConfiguration内部已经标注了@ConditionalOnMissingBean(DataSource.class)。当CustomDataSourceConfig被扫描后,dataSourceBean 被注册,自动配置的DataSourceAutoConfiguration发现容器中已有DataSource,自动跳过。
操作后运行结果及分析
场景 A:无自定义 DataSource(使用默认)
2024-05-20 14:00:22.123 INFO 12345 --- [main] o.s.b.a.h.HikariDataSourceConfiguration :
HikariPool-1 - Start completed
2024-05-20 14:00:22.456 INFO 12345 --- [main] o.s.b.a.j.JdbcTemplateAutoConfiguration :
JdbcTemplateAutoConfiguration matched: @ConditionalOnClass found JdbcTemplate
@ConditionalOnMissingBean did not find JdbcTemplate bean
场景 B:有自定义 DataSource(自动退让)
2024-05-20 14:05:33.789 INFO 12345 --- [main] o.s.b.a.a.DataSourceAutoConfiguration :
DataSourceAutoConfiguration:
Did not match:
- @ConditionalOnMissingBean (types: javax.sql.DataSource; SearchStrategy: all)
found bean 'dataSource' (OnBeanCondition)
2024-05-20 14:05:33.901 INFO 12345 --- [main] c.f.s.c.CustomDataSourceConfig :
Custom DataSource initialized with maxPoolSize=20
分析:
- 默认情况:
DataSourceAutoConfiguration检测到容器中没有DataSource,自动创建 HikariCP 连接池,连接到student_db。 - 自定义情况:
CustomDataSourceConfig被@ComponentScan扫描并注册dataSourceBean。随后(延迟导入阶段)DataSourceAutoConfiguration的@ConditionalOnMissingBean检查发现容器中已存在DataSource,自动配置被跳过。用户配置完全生效,没有冲突,没有重复 Bean。
易错场景与面试考点
易错场景一:自定义 Bean 名称与自动配置预期不一致,导致两者同时存在
// 错误示范:自定义 Bean 名称不是默认名称
package com.feixiang.student.config;
import com.zaxxer.hikari.HikariDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
@Configuration
public class BadDataSourceConfig {
@Bean("myDataSource") // ← 自定义了名称,不是默认的 "dataSource"
public DataSource myDataSource() {
return new HikariDataSource();
}
}
后果:Spring Boot 的 DataSourceAutoConfiguration 按 @ConditionalOnMissingBean(DataSource.class) 检查——是按类型检查,不是按名称。如果 myDataSource 是 DataSource 类型,条件检查发现容器中已有 DataSource,自动配置还是会跳过。这本身没问题。但如果业务代码中 @Autowired 时按名称注入 @Qualifier("dataSource"),就会找不到 Bean。
正确做法:如果不需要特殊名称,让 Bean 方法名保持默认(如 dataSource);如果确实需要自定义名称,使用 @Primary 或确保所有注入点使用正确的名称。
易错场景二:在自动配置类内部错误使用 @ConditionalOnMissingBean
// 错误示范:在自动配置类内部,试图用 @ConditionalOnMissingBean 控制同一类型的多个 Bean
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(DataSource.class)
public class BadDataSourceAutoConfiguration {
@Bean
@ConditionalOnMissingBean(DataSource.class)
public DataSource hikariDataSource() {
return new HikariDataSource();
}
@Bean
@ConditionalOnMissingBean(DataSource.class) // ← 逻辑问题!
public DataSource tomcatDataSource() {
return new org.apache.tomcat.jdbc.pool.DataSource();
}
}
后果:当容器中没有 DataSource 时,两个 @Bean 方法的 @ConditionalOnMissingBean 条件同时满足。Spring 会尝试注册两个 DataSource Bean,导致启动时报 NoUniqueBeanDefinitionException 或至少留下一个无用的 Bean(取决于 @Conditional 的处理顺序)。
正确做法:同一类型的备选 Bean 应该用互斥条件区分,例如 @ConditionalOnClass(HikariDataSource.class) 和 @ConditionalOnClass(org.apache.tomcat.jdbc.pool.DataSource.class),或者使用不同的配置类分别控制。
面试考点
Q:@ConditionalOnMissingBean 和 @ConditionalOnBean 有什么区别?
@ConditionalOnMissingBean表示"容器中不存在某 Bean 时条件成立",用于自动配置创建默认实现;@ConditionalOnBean表示"容器中存在某 Bean 时条件成立",用于在自动配置之间建立依赖关系。两者是互补的:一个用于"退让",一个用于"依赖"。
Q:为什么用户自定义的 Bean 能覆盖自动配置的 Bean?
因为
AutoConfigurationImportSelector实现了DeferredImportSelector,自动配置类在所有用户配置和组件扫描完成后才被处理。此时用户自定义的 Bean 已经存在于容器中,当自动配置类上的@ConditionalOnMissingBean检查时,会发现"容器中已有该类型的 Bean",从而跳过自动配置的默认 Bean 注册。这是 Spring Boot "用户配置优先" 的设计体现。
Q:@ConditionalOnMissingBean 的 search 属性有什么作用?
search控制搜索范围:CURRENT只在当前ApplicationContext中搜索;ANCESTORS只在父上下文中搜索;ALL同时搜索当前和父上下文。在典型的 Spring Boot 单上下文应用中通常使用默认值ALL。在 Spring MVC 父子容器场景(如传统 Spring 应用)中,这个属性可以控制条件检查的范围。
Q:如果用户和自动配置都定义了 @Primary Bean,哪个生效?
如果用户定义了同类型的 Bean,即使自动配置也标注了
@Primary,由于@ConditionalOnMissingBean的跳过机制,自动配置的 Bean 根本不会被注册。因此用户的 Bean 总是优先。如果用户定义了多个同类型 Bean,需要在用户配置中自行使用@Primary或@Qualifier消除歧义。