深入理解 Spring @Conditional 注解:原理与实战
一、什么是 @Conditional
Spring 4 引入的 @Conditional 注解,是实现 Bean 条件化注册的核心工具。它的核心作用是:根据特定条件动态决定是否初始化并向 Spring 容器注册 Bean。
这让我们可以根据环境变量、配置参数、系统属性等外部条件,灵活控制 Bean 的创建,避免了硬编码的耦合,极大提升了系统的可扩展性和适配性。
二、核心原理与定义
1. @Conditional 注解定义
java
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
Class<? extends Condition>[] value();
}
@Target:该注解可以标注在类上或方法上。value():接收一个或多个Condition接口的实现类,这些类负责具体的条件判断。
2. Condition 接口:条件判断的入口
Condition 是所有条件判断的基础接口,所有自定义条件都需要实现它。
java
@FunctionalInterface
public interface Condition {
boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata);
}
matches():核心方法,返回true则条件满足,对应的 Bean 会被注册;返回false则不会注册。ConditionContext:上下文对象,提供了丰富的系统信息,用于条件判断。AnnotatedTypeMetadata:可以获取被注解元素的元数据信息。
3. ConditionContext 上下文能力
ConditionContext 是 Spring 提供的上下文工具类,它包含了一系列实用方法来获取系统信息:
java
public interface ConditionContext {
// 获取 BeanDefinition 注册器,可用于注册或移除 Bean
BeanDefinitionRegistry getRegistry();
// 获取 Bean 工厂,可获取容器中所有 Bean
@Nullable
ConfigurableListableBeanFactory getBeanFactory();
// 获取环境变量,持有所有配置信息(如 application.properties 中的配置)
Environment getEnvironment();
// 获取资源加载器,用于加载外部资源
ResourceLoader getResourceLoader();
// 获取类加载器
@Nullable
ClassLoader getClassLoader();
}
三、实战:基于配置动态选择 Bean
我们通过一个完整的示例,演示如何根据配置文件中的参数,动态选择加载不同的随机数生成器 Bean。
1. 定义基础组件
首先定义一个通用的随机数据生成器类,它接收一个 Supplier 来生成不同类型的随机数据。
java
public class RandDataComponent<T> {
private Supplier<T> rand;
public RandDataComponent(Supplier<T> rand) {
this.rand = rand;
}
public T rand() {
return rand.get();
}
}
2. 定义配置类
在配置类中,我们定义两个 Bean:一个生成整数随机数,一个生成布尔值随机数,并通过 @Conditional 注解绑定各自的条件。
java
@Configuration
public class ConditionalAutoConfig {
@Bean
@Conditional(RandIntCondition.class)
public RandDataComponent<Integer> randIntComponent() {
return new RandDataComponent<>(() -> {
Random random = new Random();
return random.nextInt(1024);
});
}
@Bean
@Conditional(RandBooleanCondition.class)
public RandDataComponent<Boolean> randBooleanComponent() {
return new RandDataComponent<>(() -> {
Random random = new Random();
return random.nextBoolean();
});
}
}
3. 实现 Condition 条件判断
我们创建两个 Condition 的实现类,分别对应整数和布尔值的判断逻辑。它们会读取配置文件中的 conditional.rand.type 参数来决定条件是否满足。
java
public class RandBooleanCondition implements Condition {
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
String type = conditionContext.getEnvironment().getProperty("conditional.rand.type");
return "boolean".equalsIgnoreCase(type);
}
}
java
public class RandIntCondition implements Condition {
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
String type = conditionContext.getEnvironment().getProperty("conditional.rand.type");
return "int".equalsIgnoreCase(type);
}
}
4. 配置文件参数
在 application.properties 中添加配置,决定加载哪个 Bean:
properties
# int 表示加载整数随机数生成器;boolean 表示加载布尔值随机数生成器
conditional.rand.type=int
5. 运行效果
当 conditional.rand.type=int 时:
RandIntCondition#matches返回truerandIntComponent会被注册到容器中randBooleanComponent不会被注册
当修改配置为 conditional.rand.type=boolean 时:
RandBooleanCondition#matches返回truerandBooleanComponent会被注册到容器中randIntComponent不会被注册
四、扩展与应用场景
- 环境适配:根据开发、测试、生产环境加载不同的数据源配置。
- 依赖检查:当某个第三方类存在时才加载对应的 Bean。
- 多环境配置:根据操作系统、JDK 版本等信息加载不同的实现类。
- Spring Boot 自动配置 :这是
@Conditional最广泛的应用场景,Spring Boot 的@ConditionalOnClass、@ConditionalOnProperty等注解都是基于它扩展而来。
五、总结
@Conditional 注解通过 Condition 接口,为我们提供了一种强大的 Bean 条件化注册能力。它的核心思想是分离判断逻辑与 Bean 定义,让我们的代码更加灵活和可维护。
在实际项目中,合理运用 @Conditional 可以让我们的应用更好地适应不同的运行环境和业务场景。
附录:常用 @Conditional 衍生注解清单
Spring Boot 基于 @Conditional 扩展了一系列常用注解,无需自定义 Condition 实现类,可直接根据场景选用,大幅提升开发效率,以下是最常用的衍生注解整理(含核心作用、关键属性及使用示例):
| 衍生注解 | 核心作用 | 关键属性 | 使用示例 |
|---|---|---|---|
| @ConditionalOnProperty | 根据配置文件中的属性值判断是否注册 Bean(最常用) | 1. name:配置属性名(必填);2. havingValue:属性需匹配的值;3. matchIfMissing:属性不存在时是否匹配(默认 false) | @ConditionalOnProperty(name = "conditional.rand.type", havingValue = "int") |
| @ConditionalOnClass | 当类路径下存在指定类时,才注册 Bean(用于依赖检查) | value:指定的类(可多个) | @ConditionalOnClass(name = "org.springframework.data.redis.core.StringRedisTemplate") |
| @ConditionalOnMissingClass | 与 @ConditionalOnClass 相反,类路径下不存在指定类时注册 Bean | value:指定的类(可多个) | @ConditionalOnMissingClass("org.springframework.boot.web.servlet.ServletRegistrationBean") |
| @ConditionalOnBean | 当 Spring 容器中存在指定 Bean 时,才注册当前 Bean(依赖其他 Bean) | 1. value:Bean 的类型;2. name:Bean 的名称 | @ConditionalOnBean(name = "randIntComponent", value = RandDataComponent.class) |
| @ConditionalOnMissingBean | 与 @ConditionalOnBean 相反,容器中不存在指定 Bean 时注册当前 Bean(用于避免 Bean 重复定义) | 1. value:Bean 的类型;2. name:Bean 的名称;3. ignoreDefaultFilters:是否忽略默认过滤(默认 false) | @ConditionalOnMissingBean(RandDataComponent.class) |
| @ConditionalOnWebApplication | 当应用是 Web 应用(Spring MVC/Spring WebFlux)时,才注册 Bean | type:指定 Web 应用类型(SERVLET/REACTIVE/NONE,默认任意) | @ConditionalOnWebApplication(type = WebApplicationType.SERVLET) |
| @ConditionalOnNotWebApplication | 与 @ConditionalOnWebApplication 相反,非 Web 应用时注册 Bean | 无额外关键属性 | @ConditionalOnNotWebApplication |
| @ConditionalOnExpression | 通过 SpEL 表达式判断是否注册 Bean(支持复杂条件) | value:SpEL 表达式(返回 boolean) | @ConditionalOnExpression("{conditional.rand.type == 'int' \&\& {server.port} == 8080}") |
| @ConditionalOnJava | 根据 JDK 版本判断是否注册 Bean(适配不同 JDK 环境) | 1. value:JDK 版本范围;2. range:匹配规则(EQUAL_OR_NEWER/EQUAL_OR_OLDER 等) | @ConditionalOnJava(value = JavaVersion.JAVA_11, range = ConditionalOnJava.Range.EQUAL_OR_NEWER) |
| 注意:所有衍生注解均可组合使用(如 @ConditionalOnProperty + @ConditionalOnBean),满足"所有条件同时成立"时,才会注册 Bean。 |