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

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

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

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
  • 学习路径
  • 第1章 Spring概述与IoC容器

    • Spring概述与IoC容器
    • Spring Framework 概述
    • IoC 与 DI 核心概念
    • @Configuration 详解
    • @Component 详解
    • @ComponentScan 详解
    • @Import 详解
    • @Profile 详解
    • @PropertySource 详解
    • @Service 详解
    • @Repository 详解
  • 第2章 Bean的定义与依赖注入

    • Bean的定义与依赖注入
    • @Bean 详解
    • @Autowired 详解
    • @Qualifier 详解
    • @Primary 详解
    • @Resource 详解
    • @Inject 详解
    • @Named 详解
    • @Value 详解
    • @Scope 详解
    • @Lazy 详解
  • 第3章 Bean生命周期与作用域

    • Bean生命周期与作用域
    • Bean生命周期概述
    • @PostConstruct
    • @PreDestroy
    • InitializingBean
    • DisposableBean
    • BeanPostProcessor
    • BeanFactoryPostProcessor
  • 第4章 AOP面向切面编程

    • AOP面向切面编程
    • AOP核心概念
    • @EnableAspectJAutoProxy
    • @Aspect
    • @Pointcut
    • @Before
    • @After
    • @AfterReturning
    • @AfterThrowing
    • @Around
  • 第5章 数据访问与事务管理

    • 数据访问与事务管理
    • 数据访问概述
    • @EnableTransactionManagement
    • @Transactional
    • @Transactional 的传播行为
    • @Transactional 的隔离级别
    • @Transactional 的回滚规则
    • @Transactional 的超时与只读属性
    • @TransactionalEventListener
  • 第6章 Spring Boot自动配置基础

    • Spring Boot自动配置基础
    • @SpringBootApplication 注解
    • @EnableAutoConfiguration 注解
    • @ConfigurationProperties 注解
    • @ConditionalOnClass 注解
    • @ConditionalOnMissingBean 注解
    • @ConditionalOnProperty 注解
  • 第7章 从容器到Web: Spring MVC导引

    • Spring MVC 导引
  • 第8章 扩展阅读

    • 扩展阅读
    • Spring 事件机制 — ApplicationEvent / ApplicationListener
    • @EventListener
    • SpEL — Spring 表达式语言
    • 校验 Validation — JSR-303 / JSR-380 Bean Validation
    • 类型转换与数据绑定 — Converter / DataBinder
  • 附录

    • Spring Framework 专业术语
    • Spring 核心知识点
    • Spring 面试高频考点
    • Spring 核心注解速查表

@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 可以标注在:

  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。条件不匹配的配置类根本不会进入后续的实例化和依赖注入流程。

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

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

上一页
@ConfigurationProperties 注解
下一页
@ConditionalOnMissingBean 注解