Java注解@Conditional
·
Java注解@Conditional
@Conditional是 Spring Boot 自动配置的"大脑",它决定了何时、何条件下创建 Bean。让我用最通俗的方式给你讲清楚。
一句话理解 @Conditional
@Conditional 是 Spring 的"智能开关",它根据条件决定是否创建 Bean。
好比智能家居:
- 条件:
如果(有人在家 && 温度>30度)→ 打开空调 - 条件:
如果(没人在家)→ 关闭所有电器
Spring Boot 就是根据各种条件来决定:要不要创建 DataSource?要不要配置 Redis?要不要开启安全验证?
从简单到复杂
传统 Spring(手动配置)
@Configuration
public class AppConfig {
// 不管需不需要,都会创建这个Bean
@Bean
public DataSource dataSource() {
return new DataSource();
}
}
Spring Boot(智能配置)
@Configuration
public class DataSourceAutoConfiguration {
// 只有当类路径有DataSource类,并且配置了数据库连接时,才创建Bean
@Bean
@ConditionalOnClass(DataSource.class)
@ConditionalOnProperty(name = "spring.datasource.url")
public DataSource dataSource() {
return new DataSource();
}
}
@Conditional 家族
Spring Boot 提供了多种 @Conditional注解,每个都有特定用途:
| 注解 | 作用 | 示例场景 |
|---|---|---|
@ConditionalOnClass |
类路径存在指定的类 | 有 MySQL 驱动才配置 DataSource |
@ConditionalOnMissingClass |
类路径不存在指定的类 | 没有 MongoDB 驱动才用默认配置 |
@ConditionalOnBean |
Spring 容器中存在指定的 Bean | 已有 DataSource 就不重复创建 |
@ConditionalOnMissingBean |
Spring 容器中不存在指定的 Bean | 没有 DataSource 时才创建 |
@ConditionalOnProperty |
配置文件中有指定的属性 | 配置了 spring.redis.host才配置 Redis |
@ConditionalOnExpression |
SpEL 表达式为 true | 复杂的条件判断 |
@ConditionalOnJava |
指定的 Java 版本 | Java 8+ 才启用某些特性 |
@ConditionalOnWebApplication |
是 Web 应用 | Web 环境才配置 WebMvc |
@ConditionalOnNotWebApplication |
不是 Web 应用 | 非 Web 环境配置不同 |
@ConditionalOnResource |
存在指定的资源文件 | 有 schema.sql才初始化数据库 |
@ConditionalOnJndi |
存在指定的 JNDI | JNDI 环境才使用 |
@ConditionalOnCloudPlatform |
指定的云平台 | 在 Kubernetes 中才启用某些特性 |
核心注解详解
1. @ConditionalOnClass - “有才用”
@Configuration
@ConditionalOnClass({ // 必须同时存在这些类
DataSource.class,
JdbcTemplate.class,
PlatformTransactionManager.class
})
public class DataSourceAutoConfiguration {
// 只有当类路径有数据库相关类时,才配置数据源
}
实际应用:Spring Boot 根据你引入的 starter 自动判断
<!-- 引入 MySQL 驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- Spring Boot 会自动配置 DataSource -->
2. @ConditionalOnMissingBean - “没有才创”
@Configuration
public class CacheConfiguration {
@Bean
@ConditionalOnMissingBean // 如果容器中没有 CacheManager,才创建这个
public CacheManager cacheManager() {
return new SimpleCacheManager();
}
@Bean
@ConditionalOnMissingBean(name = "redisTemplate") // 没有 redisTemplate 才创建
public RedisTemplate<String, Object> redisTemplate() {
return new RedisTemplate<>();
}
}
使用场景:允许用户自定义 Bean,覆盖默认配置。
3. @ConditionalOnProperty - “配置了才用”
@Configuration
@ConditionalOnProperty(
prefix = "spring.datasource", // 配置前缀
name = "url", // 属性名
havingValue = "", // 期望的值(默认只要存在就为true)
matchIfMissing = false // 属性缺失时是否匹配
)
public class DataSourceAutoConfiguration {
// 只有当配置了 spring.datasource.url 时才生效
}
// 更常用的写法
@ConditionalOnProperty("spring.datasource.url") // 只要配置了这个属性就生效
@ConditionalOnProperty(name = "cache.enabled", havingValue = "true") // 必须为true
@ConditionalOnProperty(name = "cache.type", havingValue = "redis", matchIfMissing = true) // 默认使用redis
4. @ConditionalOnExpression - “复杂条件”
@Configuration
@ConditionalOnExpression(
"#{environment['spring.datasource.url'] != null && " +
"environment['spring.datasource.username'] != null}"
)
public class DataSourceAutoConfiguration {
// 只有同时配置了url和username才生效
}
// 或者更复杂的条件
@ConditionalOnExpression(
"'${spring.profiles.active}'.equals('prod') && " +
"${server.port} > 8080"
)
实战案例
案例1:多数据源配置
@Configuration
public class DataSourceConfig {
// 主数据源 - 默认配置
@Bean(name = "primaryDataSource")
@ConfigurationProperties(prefix = "spring.datasource.primary")
@ConditionalOnProperty(prefix = "spring.datasource.primary", name = "url")
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
}
// 从数据源 - 可选配置
@Bean(name = "secondaryDataSource")
@ConfigurationProperties(prefix = "spring.datasource.secondary")
@ConditionalOnProperty(prefix = "spring.datasource.secondary", name = "url")
public DataSource secondaryDataSource() {
return DataSourceBuilder.create().build();
}
// 内存数据库 - 开发环境使用
@Bean
@ConditionalOnMissingBean(DataSource.class) // 没有其他数据源时才创建
@ConditionalOnProperty(name = "spring.profiles.active", havingValue = "dev")
public DataSource h2DataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.addScript("classpath:schema.sql")
.build();
}
}
案例2:多缓存策略
@Configuration
public class CacheConfig {
// 默认使用本地缓存
@Bean
@ConditionalOnMissingBean(CacheManager.class)
@ConditionalOnProperty(name = "cache.type", havingValue = "local", matchIfMissing = true)
public CacheManager localCacheManager() {
return new ConcurrentMapCacheManager("users", "products");
}
// 如果配置了Redis,使用Redis缓存
@Bean
@ConditionalOnClass(RedisConnectionFactory.class)
@ConditionalOnProperty(name = "cache.type", havingValue = "redis")
public CacheManager redisCacheManager(RedisConnectionFactory connectionFactory) {
RedisCacheManager cacheManager = RedisCacheManager.create(connectionFactory);
cacheManager.setCacheNames(Arrays.asList("users", "products"));
return cacheManager;
}
// 如果配置了Caffeine,使用Caffeine缓存
@Bean
@ConditionalOnClass(Caffeine.class)
@ConditionalOnProperty(name = "cache.type", havingValue = "caffeine")
public CacheManager caffeineCacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
cacheManager.setCaffeine(Caffeine.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES)
.maximumSize(1000));
return cacheManager;
}
}
案例3:不同环境的Bean
@Configuration
public class EmailConfig {
// 开发环境:使用 Mock 邮件服务
@Bean
@ConditionalOnProperty(name = "spring.profiles.active", havingValue = "dev")
@Primary // 优先使用这个Bean
public EmailService mockEmailService() {
return new MockEmailService(); // 只打印日志,不真实发送
}
// 测试环境:使用测试邮件服务
@Bean
@ConditionalOnProperty(name = "spring.profiles.active", havingValue = "test")
@Primary
public EmailService testEmailService() {
return new TestEmailService(); // 发送到测试邮箱
}
// 生产环境:使用真实邮件服务
@Bean
@ConditionalOnProperty(name = "spring.profiles.active", havingValue = "prod")
@Primary
public EmailService prodEmailService() {
return new SmtpEmailService(); // 真实发送邮件
}
// 如果没有指定环境,默认用Mock(安全)
@Bean
@ConditionalOnMissingBean(EmailService.class)
public EmailService defaultEmailService() {
return new MockEmailService();
}
}
案例4:功能开关
@Configuration
public class FeatureConfig {
// 是否开启验证码功能
@Bean
@ConditionalOnProperty(name = "feature.captcha.enabled", havingValue = "true")
public CaptchaService captchaService() {
return new GoogleCaptchaService();
}
// 是否开启短信功能
@Bean
@ConditionalOnProperty(name = "feature.sms.enabled", havingValue = "true")
@ConditionalOnClass(name = "com.aliyun.dysmsapi20170525.Client") // 有阿里云SDK才创建
public SmsService smsService() {
return new AliyunSmsService();
}
// 是否开启支付功能
@Bean
@ConditionalOnExpression(
"#{environment['feature.payment.enabled'] == 'true' && " +
"environment['payment.alipay.app-id'] != null && " +
"environment['payment.alipay.private-key'] != null}"
)
public PaymentService paymentService() {
return new AlipayService();
}
}
案例5:自定义条件注解
// 1. 定义条件类
public class OnWindowsCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String os = context.getEnvironment().getProperty("os.name");
return os != null && os.toLowerCase().contains("win");
}
}
// 2. 创建自定义注解
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Conditional(OnWindowsCondition.class)
public @interface ConditionalOnWindows {
}
// 3. 使用自定义注解
@Configuration
public class OsSpecificConfig {
@Bean
@ConditionalOnWindows
public FileService windowsFileService() {
return new WindowsFileService(); // Windows专用实现
}
@Bean
@ConditionalOnLinux // 假设有类似的Linux条件
public FileService linuxFileService() {
return new LinuxFileService(); // Linux专用实现
}
}
源码解析
Spring Boot 自动配置的魔法
打开 spring-boot-autoconfigure包的源码:
// DataSourceAutoConfiguration.java 片段
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
@ConditionalOnMissingBean(type = "io.r2dbc.spi.ConnectionFactory")
@EnableConfigurationProperties(DataSourceProperties.class)
@Import({ DataSourcePoolMetadataProvidersConfiguration.class,
DataSourceInitializationConfiguration.class })
public class DataSourceAutoConfiguration {
@Configuration(proxyBeanMethods = false)
@Conditional(EmbeddedDatabaseCondition.class) // 内嵌数据库条件
@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
@Import(EmbeddedDataSourceConfiguration.class)
protected static class EmbeddedDatabaseConfiguration {
}
@Configuration(proxyBeanMethods = false)
@Conditional(PooledDataSourceCondition.class) // 连接池数据源条件
@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
@Import({ DataSourceConfiguration.Hikari.class, // 自动选择连接池
DataSourceConfiguration.Tomcat.class,
DataSourceConfiguration.Dbcp2.class,
DataSourceConfiguration.Generic.class,
DataSourceJmxConfiguration.class })
protected static class PooledDataSourceConfiguration {
}
}
条件注解的执行时机
// 简化版的执行逻辑
public boolean shouldCreateBean() {
// 1. 检查 @ConditionalOnClass
if (!classExists("com.mysql.cj.jdbc.Driver")) {
return false; // 没有MySQL驱动,不创建DataSource
}
// 2. 检查 @ConditionalOnProperty
if (!propertyExists("spring.datasource.url")) {
return false; // 没有配置数据库URL,不创建DataSource
}
// 3. 检查 @ConditionalOnMissingBean
if (beanExists("dataSource")) {
return false; // 已经存在dataSource Bean,不重复创建
}
// 4. 检查 @ConditionalOnWebApplication
if (!isWebApplication()) {
return false; // 不是Web应用,不创建某些Web相关的Bean
}
return true; // 所有条件都满足,创建Bean
}
条件注解的组合使用
组合条件
@Configuration
// 组合多个条件:必须是Web应用,且配置了Redis,且没有自定义的RedisTemplate
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@ConditionalOnClass(RedisOperations.class)
@ConditionalOnProperty(prefix = "spring.redis", name = "host")
@ConditionalOnMissingBean(RedisTemplate.class)
public class RedisAutoConfiguration {
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
public RedisTemplate<Object, Object> redisTemplate(
RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
条件优先级
@Configuration
public class MultiConditionConfig {
// 条件1:开发环境使用H2
@Bean
@Profile("dev") // 这是另一种条件注解
@ConditionalOnClass(name = "org.h2.Driver")
public DataSource devDataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.build();
}
// 条件2:生产环境使用MySQL
@Bean
@Profile("prod")
@ConditionalOnClass(name = "com.mysql.cj.jdbc.Driver")
@ConditionalOnProperty("spring.datasource.url")
public DataSource prodDataSource() {
return DataSourceBuilder.create().build();
}
// 条件3:如果以上都不满足,使用默认内存数据库
@Bean
@ConditionalOnMissingBean(DataSource.class)
public DataSource defaultDataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.generateUniqueName(true)
.build();
}
}
调试技巧
1. 查看哪些条件生效了
# application.yml
debug: true # 开启调试模式
启动时会输出:
============================
CONDITIONS EVALUATION REPORT
============================
Positive matches: # 匹配的条件
-----------------
DataSourceAutoConfiguration matched:
- @ConditionalOnClass found required classes 'javax.sql.DataSource',
'org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType'
(OnClassCondition)
Negative matches: # 未匹配的条件
-----------------
KafkaAutoConfiguration:
Did not match:
- @ConditionalOnClass did not find required class
'org.apache.kafka.clients.consumer.Consumer' (OnClassCondition)
2. 自定义条件日志
@Component
public class ConditionLogger implements ApplicationListener<ApplicationPreparedEvent> {
@Override
public void onApplicationEvent(ApplicationPreparedEvent event) {
ConfigurableEnvironment env = event.getApplicationContext().getEnvironment();
// 打印所有条件判断
System.out.println("=== 条件判断报告 ===");
System.out.println("是否Web应用: " +
WebApplicationTypeUtils.getWebApplicationType(event.getApplicationContext()));
System.out.println("类路径是否有DataSource: " +
ClassUtils.isPresent("javax.sql.DataSource", getClass().getClassLoader()));
System.out.println("配置了数据库URL: " +
env.containsProperty("spring.datasource.url"));
}
}
性能优化
1. 避免不必要的条件检查
// 不好:每次都要检查
@ConditionalOnExpression(
"T(org.springframework.util.StringUtils).hasText('${some.property}')"
)
// 好:使用 @ConditionalOnProperty,Spring会缓存结果
@ConditionalOnProperty("some.property")
2. 合理使用 matchIfMissing
@Configuration
// matchIfMissing = true 表示属性缺失时也匹配(默认值)
@ConditionalOnProperty(
prefix = "app.feature",
name = "advanced",
havingValue = "true",
matchIfMissing = false // 明确指定,提高可读性
)
public class AdvancedFeatureConfig {
// 只有当 app.feature.advanced=true 时才生效
}
3. 条件分组
@Configuration
// 把相关条件放在一起,便于理解和维护
@ConditionalOnWebApplication
@ConditionalOnClass({Servlet.class, DispatcherServlet.class})
@ConditionalOnProperty(prefix = "spring.mvc", name = "enabled", matchIfMissing = true)
public class WebMvcAutoConfiguration {
// Web MVC 自动配置
}
实际应用场景
场景1:多环境配置
# application-dev.yml
feature:
cache: true
mq: false # 开发环境关闭消息队列
monitor: false
# application-prod.yml
feature:
cache: true
mq: true # 生产环境开启消息队列
monitor: true
@Configuration
public class FeatureToggleConfig {
@Bean
@ConditionalOnProperty(name = "feature.cache", havingValue = "true")
public CacheService cacheService() {
return new RedisCacheService();
}
@Bean
@ConditionalOnProperty(name = "feature.mq", havingValue = "true")
@ConditionalOnClass(RabbitTemplate.class)
public MessageQueueService mqService() {
return new RabbitMQService();
}
@Bean
@ConditionalOnProperty(name = "feature.monitor", havingValue = "true")
public MonitorService monitorService() {
return new PrometheusMonitorService();
}
}
场景2:模块化配置
// 用户模块 - 只在有用户相关配置时启用
@Configuration
@ConditionalOnProperty(prefix = "module.user", name = "enabled", havingValue = "true")
@EnableConfigurationProperties(UserProperties.class)
public class UserModuleAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public UserService userService(UserProperties properties) {
return new UserServiceImpl(properties);
}
}
// 订单模块 - 只在有订单相关配置时启用
@Configuration
@ConditionalOnProperty(prefix = "module.order", name = "enabled", havingValue = "true")
@ConditionalOnClass(OrderService.class)
public class OrderModuleAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public OrderService orderService() {
return new OrderServiceImpl();
}
}
场景3:兼容性配置
@Configuration
public class CompatibilityConfig {
// Java 8 特性
@Bean
@ConditionalOnJava(range = ConditionalOnJava.Range.EQUAL_OR_NEWER, value = JavaVersion.EIGHT)
public Feature java8Feature() {
return new Java8Feature();
}
// Java 11+ 特性
@Bean
@ConditionalOnJava(range = ConditionalOnJava.Range.EQUAL_OR_NEWER, value = JavaVersion.ELEVEN)
public Feature java11Feature() {
return new Java11Feature();
}
// 旧版兼容
@Bean
@ConditionalOnClass(name = "org.hibernate.SessionFactory")
@ConditionalOnMissingBean(name = "entityManagerFactory")
public SessionFactory sessionFactory() {
return new LegacySessionFactory();
}
}
最佳实践
-
优先使用特定条件注解:
// 好:明确 @ConditionalOnProperty("app.feature.enabled") // 不好:模糊 @ConditionalOnExpression("#{environment['app.feature.enabled'] == 'true'}") -
条件组合要合理:
// 明确的优先级 @Profile("prod") // 1. 环境 @ConditionalOnClass(DataSource.class) // 2. 类路径 @ConditionalOnProperty("db.url") // 3. 配置 @ConditionalOnMissingBean // 4. 容器状态 -
提供合理的默认值:
@Bean @ConditionalOnMissingBean // 允许用户自定义覆盖 public CacheService cacheService() { return new DefaultCacheService(); } -
条件要可测试:
@Test @EnabledIfSystemProperty(named = "os.arch", matches = ".*64.*") void test64BitOnly() { // 只在64位系统运行的测试 }
总结:@Conditional是 Spring Boot 自动配置的基石,它让 Spring Boot 能够:
- 智能判断:根据环境、配置、类路径自动决策
- 避免冲突:防止重复创建 Bean
- 灵活配置:支持多种条件组合
- 便于扩展:支持自定义条件注解
记住核心思想:“条件满足才创建,不满足就不创建”,这就是 Spring Boot 自动配置的智慧所在。
更多推荐




所有评论(0)