网站Logo 苏叶的belog

Spring条件注解

wdadwa
0
2026-03-16

一,快速入门

1.1 什么是 Spring 条件注解

Spring Framework 中,** 条件注解(Conditional Annotation)** 用于 根据运行时条件决定某个 Bean 是否被注册到 IoC 容器中

本质机制:

只有当指定条件成立时,Spring 才会创建并注册该 Bean。

底层依赖核心接口:

org.springframework.context.annotation.Condition

Spring 在解析 @Configuration@Bean 时,会调用 Condition 判断条件是否成立。

核心注解:

@Conditional

1.2 为什么需要条件注解

在实际工程中,经常会出现:

场景需求
多环境部署dev / test / prod
多数据库支持MySQL / PostgreSQL
自动装配有某个类才启用某组件
可选功能模块开关控制
Spring Boot Starter自动配置

例如:

如果项目中存在 Redis 依赖 -> 才加载 Redis 配置
如果配置文件开启 feature=true -> 才启用功能

1.3 条件注解的核心结构

Condition接口

public interface Condition {

    boolean matches(
        ConditionContext context,
        AnnotatedTypeMetadata metadata
    );
}

参数说明

参数作用
ConditionContextSpring上下文
Environment读取配置
BeanFactoryBean工厂
ClassLoader类加载器
AnnotatedTypeMetadata注解元数据

返回值:

true  -> 注册Bean
false -> 不注册

1.4 Spring原生条件注解

Spring Core 中,只有一个:

@Conditional

但在 Spring Boot 中扩展出了大量条件注解(自动配置核心)。

常见如下:

注解作用
@ConditionalOnClass类存在才生效
@ConditionalOnMissingBeanBean不存在才创建
@ConditionalOnBeanBean存在才创建
@ConditionalOnProperty配置存在
@ConditionalOnExpressionSpEL表达式
@ConditionalOnJavaJava版本
@ConditionalOnResource资源存在
@ConditionalOnWebApplicationWeb环境

这些全部底层都是:

@Conditional

@ConditionalOnClass

作用:只有当 classpath 中存在指定类 时才加载配置。

常用于:Starter 自动配置

例如:只有存在 Redis 依赖才加载 Redis 配置。

Demo

@Configuration
@ConditionalOnClass(name = "redis.clients.jedis.Jedis")
public class RedisAutoConfiguration {

    @Bean
    public RedisClient redisClient() {
        return new RedisClient();
    }

}

逻辑

classpath 存在 Jedis
        ↓
RedisAutoConfiguration 生效
        ↓
创建 redisClient

如果项目 没有引入 Redis 依赖

该配置不会加载

@ConditionalOnMissingBean

作用:只有 Bean 不存在才创建

避免 自动配置覆盖用户自定义 Bean

Demo

@Configuration
public class JacksonAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public ObjectMapper objectMapper() {
        return new ObjectMapper();
    }

}

自定义

@Bean
public ObjectMapper objectMapper(){
    ObjectMapper mapper = new ObjectMapper();
    mapper.enable(SerializationFeature.INDENT_OUTPUT);
    return mapper;
}

结果

用户定义 Bean
↓
自动配置不生效

这是 Spring Boot 扩展机制核心

@ConditionalOnBean

作用: 只有当某个 Bean 已存在才创建

Demo

@Configuration
public class TransactionConfiguration {

    @Bean
    @ConditionalOnBean(DataSource.class)
    public TransactionManager transactionManager(DataSource dataSource) {
        return new TransactionManager(dataSource);
    }

}

逻辑

存在 DataSource
        ↓
创建 TransactionManager

如果项目:没有配置数据库

则:TransactionManager 不会创建

@ConditionalOnProperty

作用 根据 配置文件属性 控制 Bean。

常用于:功能开关

application.yml

feature:
  sms:
    enabled: true

Demo

@Configuration
public class SmsConfiguration {

    @Bean
    @ConditionalOnProperty(
            prefix = "feature.sms",
            name = "enabled",
            havingValue = "true"
    )
    public SmsService smsService() {
        return new SmsService();
    }

}

逻辑

feature.sms.enabled=true
        ↓
创建 SmsService

如果:false

则:Bean 不加载

@ConditionalOnExpression

作用 :通过 SpEL 表达式控制条件。

Demo

@Configuration
public class ExpressionConfig {

    @Bean
    @ConditionalOnExpression("${server.port} == 8080")
    public PortService portService() {
        return new PortService();
    }

}

逻辑

server.port == 8080
        ↓
创建 PortService

@ConditionalOnJava

作用:根据 Java 版本判断。

Demo

@Configuration
@ConditionalOnJava(JavaVersion.SEVENTEEN)
public class Java17Configuration {

    @Bean
    public JavaFeatureService javaFeatureService() {
        return new JavaFeatureService();
    }

}

逻辑

JDK >= 17
        ↓
加载配置

常用于:JDK 特性适配

@ConditionalOnResource

作用 :判断 资源文件是否存在

Demo

@Configuration
public class ResourceConfiguration {

    @Bean
    @ConditionalOnResource(resources = "classpath:config/app.properties")
    public ResourceService resourceService() {
        return new ResourceService();
    }

}

逻辑

classpath 存在 config/app.properties
        ↓
创建 ResourceService

常用于:检测配置文件

@ConditionalOnWebApplication

作用 :只有在 Web 应用环境下生效。

Demo

@Configuration
@ConditionalOnWebApplication
public class WebMvcConfiguration {

    @Bean
    public WebService webService() {
        return new WebService();
    }

}

逻辑

SpringBoot Web项目
        ↓
配置生效

如果是:非 Web 项目

则:配置不会加载

@ConditionalOnNotWebApplication

与上面相反:

@Configuration
@ConditionalOnNotWebApplication
public class CliConfiguration {

    @Bean
    public CliService cliService() {
        return new CliService();
    }

}

适用于:

CLI工具
批处理程序

1.5 自定义条件注解

我们自己实现一个:

只有 Linux 才加载 Bean
  1. 定义 Condition

    public class LinuxCondition implements Condition {
    
        @Override
        public boolean matches(
                ConditionContext context,
                AnnotatedTypeMetadata metadata) {
    
            String osName = context
                    .getEnvironment()
                    .getProperty("os.name");
    
            return osName != null && osName.contains("Linux");
        }
    }
    

    逻辑:读取系统变量,判断是否 Linux

  2. 使用 @Conditional

    @Configuration
    public class AppConfig {
    
        @Bean
        @Conditional(LinuxCondition.class)
        public UserService userService() {
            return new UserService();
        }
    }
    

    效果:

    Linux -> Bean 创建
    Windows -> Bean 不创建

  3. 测试

    AnnotationConfigApplicationContext context =
            new AnnotationConfigApplicationContext(AppConfig.class);
    
    System.out.println(context.containsBean("userService"));
    

    输出:true / false;取决于操作系统。

常见使用方式

  • Bean 级别

    @Bean
    @Conditional(LinuxCondition.class)
    public UserService userService(){
        return new UserService();
    }
    

    只控制当前 Bean。

  • Configuration 级别

    @Configuration
    @Conditional(LinuxCondition.class)
    public class LinuxConfig {
    
        @Bean
        public UserService userService(){
            return new UserService();
        }
    
    }
    

    作用:整个配置类生效

1.6 Spring Boot 常见条件注解

@ConditionalOnClass

类存在才加载

常用于:

Starter自动配置

示例:

@Configuration
@ConditionalOnClass(name="redis.clients.jedis.Jedis")
public class RedisAutoConfiguration {

}

逻辑:

classpath存在Jedis -> 自动配置Redis

@ConditionalOnMissingBean

Bean不存在才创建

最常见。

示例:

@Bean
@ConditionalOnMissingBean
public ObjectMapper objectMapper(){
    return new ObjectMapper();
}

意义:

用户自己定义Bean -> 不覆盖
用户没定义 -> 自动创建

@ConditionalOnProperty

根据配置文件。

application.yml
feature:
  enable: true

代码:

@Bean
@ConditionalOnProperty(
        name="feature.enable",
        havingValue="true"
)
public FeatureService featureService(){
    return new FeatureService();
}

1.7 业务场景用法

这是最常见的使用场景。

application.yml

feature:
  sms: true

Condition

public class SmsCondition implements Condition {

    @Override
    public boolean matches(
            ConditionContext context,
            AnnotatedTypeMetadata metadata) {

        return Boolean.parseBoolean(
            context.getEnvironment()
                .getProperty("feature.sms","false")
        );
    }
}

Bean

@Bean
@Conditional(SmsCondition.class)
public SmsService smsService(){
    return new SmsService();
}

效果:

feature.sms=true  -> 启用短信服务
feature.sms=false -> 不加载

二,在 Spring Boot 中的用途

Spring Boot 自动配置核心机制。

例如:

spring-boot-autoconfigure

内部逻辑:

1 判断classpath
2 判断Bean是否存在
3 判断配置
4 决定是否装配Bean

典型例子:

DataSourceAutoConfiguration

当:

存在 DataSource
存在 JDBC
存在配置

才会自动配置数据库。

三,条件注解执行流程

Spring 启动:

ConfigurationClassPostProcessor
        ↓
解析@Configuration
        ↓
解析@Bean
        ↓
检查@Conditional
        ↓
调用Condition.matches()
        ↓
true -> 注册Bean
false -> 忽略

四,封装自定义条件注解

可以封装自定义条件注解。

例如:

@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Conditional(LinuxCondition.class)
public @interface ConditionalOnLinux {
}

使用:

@Bean
@ConditionalOnLinux
public UserService userService(){
    return new UserService();
}

五,ConditionEvaluationReport 条件评估报告

Spring Boot 中,每一个条件判断都会被记录。

类:

ConditionEvaluationReport

作用:

记录:
哪个自动配置生效
哪个自动配置未生效
原因是什么

5.1 启用调试模式

配置:

debug=true

启动日志:

============================
CONDITIONS EVALUATION REPORT
============================

示例输出:

Positive matches:
-----------------

DataSourceAutoConfiguration matched

   - @ConditionalOnClass found required class 'javax.sql.DataSource'

Negative matches:
-----------------

MongoAutoConfiguration

   - @ConditionalOnClass did not find required class 'com.mongodb.MongoClient'

含义:

DataSource 自动配置 -> 生效
Mongo 自动配置 -> 不生效

5.2 条件报告生成流程

启动时:

ConditionEvaluator
        ↓
Condition.matches()
        ↓
记录结果
        ↓
ConditionEvaluationReport

关键类:

ConditionEvaluationReport
ConditionEvaluationReportLoggingListener

六,Spring Boot自动装配

6.1 Spring Boot 自动配置如何利用 Conditional 实现

Spring Boot 中,自动配置的核心目标是:

根据当前项目环境自动装配 Bean,而不是强制装配。

自动配置典型结构:

@Configuration
@ConditionalOnClass(DataSource.class)
@EnableConfigurationProperties(DataSourceProperties.class)
public class DataSourceAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public DataSource dataSource() {
        return createDataSource();
    }

}

执行逻辑:

Spring Boot 启动
        ↓
加载 AutoConfiguration
        ↓
检查 Conditional 条件
        ↓
条件成立 -> 注册 Bean
条件不成立 -> 跳过

自动配置入口来自:

META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

旧版本(Boot 2.x):

META-INF/spring.factories

例如:

com.example.RedisAutoConfiguration
com.example.MysqlAutoConfiguration

启动时:

AutoConfigurationImportSelector
        ↓
加载 AutoConfiguration
        ↓
逐个解析 Conditional

核心类:

AutoConfigurationImportSelector
ConfigurationClassParser
ConditionEvaluator

6.2 手写Spring Boot Starter

目标:

my-redis-spring-boot-starter

功能:

自动配置 RedisClient

项目结构

my-redis-spring-boot-starter
│
├── my-redis-autoconfigure
│      RedisAutoConfiguration
│
├── my-redis-spring-boot-starter
│
└── pom.xml

AutoConfiguration

@Configuration
@ConditionalOnClass(RedisClient.class)
@EnableConfigurationProperties(RedisProperties.class)
public class RedisAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public RedisClient redisClient(RedisProperties properties){

        RedisClient client = new RedisClient();
        client.setHost(properties.getHost());
        client.setPort(properties.getPort());

        return client;
    }

}

配置类

@ConfigurationProperties(prefix="my.redis")
public class RedisProperties {

    private String host = "localhost";

    private int port = 6379;

    // getter setter

}

注册自动配置

文件:

META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

内容:

com.example.RedisAutoConfiguration

使用 Starter

应用项目:

<dependency>
    <groupId>com.example</groupId>
    <artifactId>my-redis-spring-boot-starter</artifactId>
</dependency>

配置:

my:
  redis:
    host: 127.0.0.1
    port: 6379

Spring Boot 自动:

创建 RedisClient

七,Conditional 最重要的设计思想

Spring Boot 的自动配置哲学:

约定优于配置

自动配置遵循三条原则:

1 类存在
2 Bean不存在
3 配置开启

即:

@ConditionalOnClass
@ConditionalOnMissingBean
@ConditionalOnProperty

八,条件注解失效原因

最核心原因只有一个:

BeanDefinition 注册顺序问题

具体表现为:

原因解释
配置类加载顺序BeanDefinition 未注册
父子容器Bean 不在同一个 Context
FactoryBean判断对象不同
Bean 覆盖BeanDefinition 被替换
proxyBeanMethodsBean 创建方式变化

8.1 配置类加载顺序

问题场景

@Configuration
public class AConfig {

    @Bean
    public ServiceA serviceA() {
        return new ServiceA();
    }
}
@Configuration
public class BConfig {

    @Bean
    @ConditionalOnBean(ServiceA.class)
    public ServiceB serviceB() {
        return new ServiceB();
    }
}

理论上:

ServiceA 存在
→ ServiceB 应该创建

但在实际运行中,有时:

ServiceB 没有创建

@ConditionalOnBean 条件判断失败


根本原因:BeanDefinition 解析顺序

Spring 在启动时并不是:

先创建 Bean
再判断 Conditional

而是:

先解析 BeanDefinition
再注册 BeanDefinition
最后实例化 Bean

关键流程:

ConfigurationClassParser
        ↓
解析@Configuration
        ↓
注册 BeanDefinition
        ↓
执行 Condition

@ConditionalOnBean 判断的是:

BeanDefinition 是否存在

而不是:

Bean 实例是否存在

因此问题往往是:

判断时 BeanDefinition 还没有注册

解决方案

  1. @AutoConfigureAfter

    如果是自动配置:

    @Configuration
    @AutoConfigureAfter(AutoConfigA.class)
    public class AutoConfigB {
    }
    

    作用:确保 AutoConfigA 先加载

  2. 使用 @ConditionalOnBean(name=...)

    Spring 的 Bean 判断有三种方式:

    类型
    名称
    注解
    

    示例:

    @ConditionalOnBean(name="serviceA")
    

    有时名称判断更稳定。

  3. 使用 @Import

    @Configuration
    @Import(AConfig.class)
    public class BConfig {
    }
    

    这样:AConfig 一定先加载

8.2 父子容器

Spring 有多个容器:

Root Context
Web Context

例如:

Spring MVC

结构:

Root ApplicationContext
        │
        └── DispatcherServlet Context

如果:

ServiceA 在 RootContext
ServiceB 在 WebContext

可能导致:

@ConditionalOnBean 判断失败

8.3 FactoryBean

如果 Bean 是:

FactoryBean

Spring 实际注册的是:

factoryBeanName

而不是:

productBean

例如:

&redisConnectionFactory

所以:

@ConditionalOnBean(RedisConnectionFactory.class)

可能失效。

8.4 Bean 覆盖

如果:

用户定义 Bean

并且:

spring.main.allow-bean-definition-overriding=true

则可能:

原 BeanDefinition 被覆盖

导致 Conditional 判断异常。

8.5 proxyBeanMethods

Spring @Configuration 默认:

proxyBeanMethods = true

如果关闭:

@Configuration(proxyBeanMethods = false)

可能导致:

Bean 注册行为不同

某些 Conditional 判断可能受影响。

8.6 调试 Conditional 失效

最有效方法:

开启 debug。

debug=true

Spring 会打印:

CONDITIONS EVALUATION REPORT

示例:

ServiceBAutoConfiguration

Did not match:
    - @ConditionalOnBean (types: ServiceA) did not find any beans

说明:

Spring 在判断时没有找到 ServiceA

核心类:

ConditionEvaluationReport

8.7 Spring Boot 自动配置的正确写法

Spring 官方推荐:

@Configuration
@ConditionalOnClass(ServiceA.class)
public class ServiceBAutoConfiguration {

    @Bean
    @ConditionalOnBean(ServiceA.class)
    @ConditionalOnMissingBean
    public ServiceB serviceB(){
        return new ServiceB();
    }

}

三层保护:

Class 存在
Bean 存在
Bean 未定义
动物装饰