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

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

一句话定位:@ConditionalOnClass 是 Spring Boot 自动配置的类路径探测器,它告诉 Spring:"只有当指定的类存在于类路径中时,才注册当前 Bean 或配置类。" 这是实现"按需自动配置"的第一道闸门。


定义与作用

@ConditionalOnClass 是 Spring Boot 条件注解家族的核心成员,属于 @Conditional 的派生实现。它的判断逻辑极其纯粹:检查类加载器能否加载到指定的类。

当条件满足时(类存在),被标注的配置类或 @Bean 方法会被正常处理;当条件不满足时(类不存在),Spring 会静默跳过,不会报错,也不会注册对应的 Bean。

在自动配置中的角色

自动配置类加载流程(简化)
  ↓
读取 spring.factories 中的候选配置类
  ↓
@ConditionalOnClass 判断:类路径有依赖吗?
  ↓ 是 → 继续后续条件判断
  ↓ 否 → 直接跳过,不注册

典型应用场景:

  • DataSourceAutoConfiguration 上标注 @ConditionalOnClass({DataSource.class, HikariDataSource.class}),确保只有引入 JDBC 和连接池依赖时才配置数据源
  • RedisAutoConfiguration 上标注 @ConditionalOnClass(RedisOperations.class),确保 spring-data-redis 在类路径中才生效

适用位置与常用属性

适用位置

@ConditionalOnClass 可以标注在:

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

// 方法级别:单个 Bean 受控
@Configuration
public class DatabaseConfig {
    @Bean
    @ConditionalOnClass(name = "com.mysql.cj.jdbc.Driver")
    public DataSource mysqlDataSource() { ... }
}

常用属性

属性类型说明
valueClass<?>[]指定必须存在的类(类型安全,编译期检查)
nameString[]指定必须存在的类的全限定名(字符串形式,用于避免类路径问题)

重要区别:value 使用 Class 字面量,编译时就能检查类是否存在,但如果该类不在类路径中会导致编译失败;name 使用字符串,即使类不存在也能编译通过,更适合可选依赖场景。


核心原理

类加载探测机制

在自动配置决策链中的位置

关键理解:@ConditionalOnClass 通常是条件链的第一道关卡。如果类路径中根本不存在相关依赖,后续所有条件判断都没有意义,直接跳过可以节省启动时间和避免 ClassNotFoundException。


完整示例

场景简述

飞翔科技公司的学生成绩管理系统需要支持可选的缓存功能。架构师白歌提出需求:如果项目中引入了 Redis 依赖(spring-boot-starter-data-redis),则自动启用 Redis 缓存;如果没有引入,则系统以无缓存模式运行,不能报错。

小崔需要在 CacheConfig 配置类上实现这个"有则启用,无则跳过"的逻辑。

操作前:硬编码依赖导致启动失败

// 操作前:错误示范,未使用条件注解
package com.feixiang.student.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;

@Configuration
public class BadCacheConfig {
    
    @Bean
    public RedisCacheManager cacheManager(RedisConnectionFactory factory) {
        return RedisCacheManager.builder(factory).build();
    }
}

后果:当项目未引入 spring-boot-starter-data-redis 时,RedisCacheManager 和 RedisConnectionFactory 不在类路径中。Spring 尝试加载 BadCacheConfig 类时直接抛出 ClassNotFoundException,应用启动失败。

Caused by: java.lang.ClassNotFoundException: org.springframework.data.redis.cache.RedisCacheManager
    at java.net.URLClassLoader.findClass(URLClassLoader.java:382)
    ...

小崔被白歌叫去开会:"为什么去掉 Redis 依赖后项目起不来了?缓存应该是可选的!"

使用该注解的完整代码

小崔改用 @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 org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;

/**
 * 缓存自动配置类
 * 只有当 Redis 相关类存在于类路径中时,本配置才生效
 */
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({RedisCacheManager.class, RedisConnectionFactory.class})
public class CacheAutoConfiguration {
    
    @Bean
    public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
        return RedisCacheManager.builder(connectionFactory)
                .cacheDefaults(
                    org.springframework.data.redis.cache.RedisCacheConfiguration
                        .defaultCacheConfig()
                        .prefixKeysWith("feixiang:student:")
                )
                .build();
    }
}

同时,在 pom.xml 中,Redis 依赖是可选的:

<!-- pom.xml -->
<dependencies>
    <!-- 核心依赖(必须) -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
    <!-- Redis 依赖(可选) -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
        <optional>true</optional>  <!-- 标记为可选 -->
    </dependency>
</dependencies>

业务代码中通过 @Autowired(required = false) 安全注入:

package com.feixiang.student.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.stereotype.Service;

@Service
public class StudentService {
    
    private final RedisCacheManager cacheManager;
    
    @Autowired
    public StudentService(@Autowired(required = false) RedisCacheManager cacheManager) {
        this.cacheManager = cacheManager;
    }
    
    public String queryScore(Long studentId) {
        if (cacheManager != null) {
            // 使用缓存
            return "从 Redis 缓存查询学生 " + studentId + " 的成绩";
        } else {
            // 无缓存模式
            return "直接从数据库查询学生 " + studentId + " 的成绩";
        }
    }
}

操作后运行结果及分析

场景 A:引入 Redis 依赖

2024-05-20 10:00:12.345  INFO 12345 --- [main] c.f.s.c.CacheAutoConfiguration           : 
    CacheAutoConfiguration matched: @ConditionalOnClass found classes 
    [org.springframework.data.redis.cache.RedisCacheManager, 
     org.springframework.data.redis.connection.RedisConnectionFactory]
2024-05-20 10:00:12.567  INFO 12345 --- [main] o.s.b.a.h.HikariDataSourceConfiguration  : HikariPool-1 - Start completed
2024-05-20 10:00:12.789  INFO 12345 --- [main] c.f.s.c.CacheAutoConfiguration           : RedisCacheManager bean registered

场景 B:未引入 Redis 依赖

2024-05-20 10:05:33.123  INFO 12345 --- [main] o.s.b.a.c.AutoConfigurationReport        : 
    CacheAutoConfiguration:
      Did not match:
         - @ConditionalOnClass did not find required classes 
           'org.springframework.data.redis.cache.RedisCacheManager', 
           'org.springframework.data.redis.connection.RedisConnectionFactory'
2024-05-20 10:05:33.456  INFO 12345 --- [main] c.f.s.StudentManagementApplication       : 
    Started StudentManagementApplication in 1.234 seconds

分析:

  1. 条件匹配时:@ConditionalOnClass 检测到 RedisCacheManager 和 RedisConnectionFactory 均存在于类路径,CacheAutoConfiguration 被完整加载,cacheManager Bean 被注册。
  2. 条件不匹配时:@ConditionalOnClass 检测失败,CacheAutoConfiguration 被整体跳过,不会尝试加载其中任何 @Bean 方法,因此不会抛出 ClassNotFoundException。应用正常启动,只是没有缓存功能。

易错场景与面试考点

易错场景一:使用 value 引用不在类路径中的类,导致编译失败

小崔最初尝试这样写:

// 错误示范:使用 Class 字面量引用可选依赖
package com.feixiang.student.config;

import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheManager;  // ← 编译错误!

@Configuration
@ConditionalOnClass(RedisCacheManager.class)  // ← 如果 Redis 不在类路径,这里编译不过
public class CacheAutoConfiguration { ... }

后果:当 pom.xml 中注释掉 Redis 依赖后,import org.springframework.data.redis.cache.RedisCacheManager 这一行直接编译报错,项目无法构建。

正确做法:对于可选依赖,必须使用 name 属性(字符串形式):

@Configuration
@ConditionalOnClass(name = {
    "org.springframework.data.redis.cache.RedisCacheManager",
    "org.springframework.data.redis.connection.RedisConnectionFactory"
})
public class CacheAutoConfiguration { ... }

这样即使 Redis 不在类路径中,编译也能通过,条件判断在运行时通过类名字符串进行加载测试。

易错场景二:条件注解与 @Bean 方法参数类型不匹配

// 错误示范:类级别条件通过,但方法参数类不存在
@Configuration
@ConditionalOnClass(name = "org.springframework.data.redis.cache.RedisCacheManager")
public class BadCacheConfig {
    
    @Bean
    // 问题:RedisConnectionFactory 可能也不在类路径中!
    public RedisCacheManager cacheManager(RedisConnectionFactory factory) {
        return RedisCacheManager.builder(factory).build();
    }
}

后果:虽然类级别的 @ConditionalOnClass 通过了,但如果 RedisConnectionFactory 不在类路径中(例如依赖引入不完整),Spring 尝试解析方法参数类型时仍会抛出 ClassNotFoundException。

正确做法:方法参数涉及的类也应在类级别或方法级别进行条件控制,或者确保 name 数组中包含所有相关类:

@Configuration
@ConditionalOnClass(name = {
    "org.springframework.data.redis.cache.RedisCacheManager",
    "org.springframework.data.redis.connection.RedisConnectionFactory"  // 参数类型也要检查
})
public class CacheAutoConfiguration { ... }

面试考点

Q:@ConditionalOnClass 和 @ConditionalOnBean 有什么区别?

@ConditionalOnClass 判断的是类路径中是否存在某个类(静态检查,与当前容器状态无关);@ConditionalOnBean 判断的是当前 Spring 容器中是否已经注册了某个 Bean(动态检查,与容器状态相关)。前者用于决定是否加载某套自动配置,后者用于在自动配置之间建立依赖关系。

Q:为什么 @ConditionalOnClass 的 name 属性使用字符串而不是 Class?

如果使用 value = Class.class,Java 编译器会尝试解析该类的符号引用。如果类不在类路径中,编译阶段就会失败。使用 name = "全限定类名" 时,条件判断推迟到运行时通过 ClassLoader 或 ClassUtils.forName() 进行,即使类不存在也能正常编译,只是条件不匹配时跳过。

Q:@ConditionalOnClass 的判断发生在 Spring 生命周期的哪个阶段?

发生在 BeanDefinition 注册阶段,即容器启动的早期。Spring 在解析配置类时,会先评估所有 @Conditional 条件,只有条件匹配的配置类和 @Bean 方法才会被注册为 BeanDefinition。条件不匹配的配置类根本不会进入后续的实例化和依赖注入流程(详见Spring Core的IoC容器负责Bean实例化机制)。

Q:能否在普通 @Component 类上使用 @ConditionalOnClass?

可以,但通常不推荐。@ConditionalOnClass 设计初衷是用于自动配置类(@Configuration)。如果在 @Service 或 @Component 上使用,虽然 Spring Boot 的条件评估机制也能处理,但会让业务代码与基础设施判断耦合,破坏分层清晰性。

上一页
@EnableAutoConfiguration
下一页
@ConditionalOnMissingBean