乐途乐途
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
  • 学习路径
  • 第1章 Spring Boot 概述

    • 章节导读:Spring Boot概述与核心理念
    • Spring Boot是什么
    • Spring Boot与Spring Framework的关系
    • 约定优于配置
  • 第2章 快速入门与第一个应用

    • 章节导读:快速入门与第一个应用
    • SpringApplication
    • 第一个Spring Boot应用
  • 第3章 起步依赖与版本管理

    • 章节导读:起步依赖与版本管理
    • 起步依赖
    • BOM版本管理
  • 第4章 自动配置原理

    • 章节导读:自动配置原理
    • 自动配置原理
    • AutoConfigurationImportSelector
    • 自动配置报告
  • 第5章 核心注解

    • 章节导读:核心注解
    • @SpringBootApplication
    • @EnableAutoConfiguration
    • @ConditionalOnClass
    • @ConditionalOnMissingBean
    • @ConditionalOnBean
    • @ConditionalOnProperty
  • 第6章 外部化配置与属性绑定

    • 章节导读:外部化配置与属性绑定
    • 外部化配置
    • @ConfigurationProperties
    • @Value
    • 配置属性优先级
  • 第7章 Profile 与环境切换

    • 章节导读:Profile与环境切换
    • @Profile
    • 多环境配置文件
  • 第8章 内嵌服务器与部署

    • 章节导读:内嵌服务器与部署
    • 内嵌服务器
    • Fat Jar
  • 第9章 Actuator 与监控

    • 章节导读:Actuator与监控
    • Actuator Health
    • Actuator Info
    • Actuator Metrics
    • 自定义Endpoint
    • 自定义HealthIndicator
  • 第10章 开发工具与最佳实践

    • 章节导读:开发工具与最佳实践
    • Banner自定义
    • 热部署

一句话定位:@ConditionalOnMissingBean 是 Spring Boot 自动配置的用户配置优先守卫,它确保只有当容器中尚不存在某类型的 Bean 时,自动配置才创建默认实现;一旦用户自定义了同类 Bean,自动配置便优雅退让,实现"用户覆盖默认"的约定。


定义与作用

@ConditionalOnMissingBean 是 Spring Boot 条件注解家族中最能体现**"约定优于配置,配置优于硬编码"** 哲学的注解。它的判断逻辑是:检查当前 Spring 容器中是否已经存在指定类型或名称的 Bean。

  • 如果不存在(Missing)→ 条件满足 → 自动配置创建默认 Bean
  • 如果已存在 → 条件不满足 → 自动配置跳过,用户自定义 Bean 生效

在自动配置中的核心角色

自动配置类中的典型条件链
  ↓
@ConditionalOnClass          → 类路径有依赖吗?
  ↓ 是
@ConditionalOnProperty       → 配置开关打开了吗?
  ↓ 是
@ConditionalOnMissingBean    → 用户已经自定义了吗?
  ↓ 否 → 创建默认 Bean
  ↓ 是 → 跳过,用户 Bean 优先

典型应用场景:

  • DataSourceAutoConfiguration 内部使用 @ConditionalOnMissingBean(DataSource.class),允许用户通过自定义 DataSource Bean 覆盖默认的 HikariCP 配置
  • WebMvcAutoConfiguration 使用 @ConditionalOnMissingBean(WebMvcConfigurationSupport.class),允许用户通过继承 WebMvcConfigurationSupport 完全接管 MVC 配置

适用位置与常用属性

适用位置

@ConditionalOnMissingBean 可以标注在:

  1. 类级别:控制整个配置类是否生效
  2. @Bean 方法级别:控制单个默认 Bean 是否注册
// 类级别:整个配置类受控
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(DataSource.class)
public class PooledDataSourceConfiguration { ... }

// 方法级别:单个默认 Bean 受控
@Configuration
public class CustomConfig {
    @Bean
    @ConditionalOnMissingBean(name = "studentDataSource")
    public DataSource defaultDataSource() { ... }
}

常用属性

属性类型说明
valueClass<?>[]指定必须不存在的 Bean 类型(类型安全)
nameString[]指定必须不存在的 Bean 名称(按名称匹配)
typeString[]指定必须不存在的 Bean 类型全限定名(字符串形式)
searchSearchStrategy搜索策略:CURRENT(当前上下文)、ANCESTORS(父上下文)、ALL(全部)
annotationClass<? 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 被扫描后,dataSource Bean 被注册,自动配置的 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

分析:

  1. 默认情况:DataSourceAutoConfiguration 检测到容器中没有 DataSource,自动创建 HikariCP 连接池,连接到 student_db。
  2. 自定义情况:CustomDataSourceConfig 被 @ComponentScan 扫描并注册 dataSource Bean。随后(延迟导入阶段)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 消除歧义。

上一页
@ConditionalOnClass
下一页
@ConditionalOnBean