@ConditionalOnClass 注解
一句话定位:
@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 可以标注在:
- 类级别:控制整个配置类是否生效
- @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() { ... }
}
常用属性
| 属性 | 类型 | 说明 |
|---|---|---|
value | Class<?>[] | 指定必须存在的类(类型安全,编译期检查) |
name | String[] | 指定必须存在的类的全限定名(字符串形式,用于避免类路径问题) |
重要区别:
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
分析:
- 条件匹配时:
@ConditionalOnClass检测到RedisCacheManager和RedisConnectionFactory均存在于类路径,CacheAutoConfiguration被完整加载,cacheManagerBean 被注册。 - 条件不匹配时:
@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。条件不匹配的配置类根本不会进入后续的实例化和依赖注入流程。
Q:能否在普通 @Component 类上使用 @ConditionalOnClass?
可以,但通常不推荐。
@ConditionalOnClass设计初衷是用于自动配置类(@Configuration)。如果在@Service或@Component上使用,虽然 Spring Boot 的条件评估机制也能处理,但会让业务代码与基础设施判断耦合,破坏分层清晰性。