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

    • 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自定义
    • 热部署

一句话定位:@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 可以标注在:

  1. 类级别:控制整个配置类是否生效
  2. @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();
    }
}

常用属性

属性类型说明
valueClass<?>[]指定必须存在的 Bean 类型(类型安全)
nameString[]指定必须存在的 Bean 名称(按名称匹配)
typeString[]指定必须存在的 Bean 类型全限定名(字符串形式)
searchSearchStrategy搜索策略:CURRENT、ANCESTORS、ALL
annotationClass<? 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

分析:

  1. 有数据源时:DataSourceAutoConfiguration 检测到 HikariDataSource 在类路径且配置完整,注册 DataSource Bean。随后 HealthCheckAutoConfiguration 的 @ConditionalOnBean(DataSource.class) 检查通过,注册 DatabaseHealthIndicator。
  2. 无数据源时:DataSourceAutoConfiguration 因缺少配置或依赖被跳过,容器中没有 DataSource。HealthCheckAutoConfiguration 的 @ConditionalOnBean 检查失败,被静默跳过,不会报错。
  3. 依赖链解耦: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 差异不明显。

上一页
@ConditionalOnMissingBean
下一页
@ConditionalOnProperty