有道无术,术尚可求,有术无道,止于术。
本系列Spring Boot版本2.7.0
在上篇文档中,我们回顾了Spring 中创建Bean 的各种方式,如果我们需要根据其他条件或者配置项来控制是否注册一个Bean,应该怎么办呢?
如果是通过@Import导入一个选择器,获取条件再判断是否注册,倒是可以实现,但是十分麻烦。
Spring Boot已经为我们提供了@Conditional及一系列的派生注解,实现条件控制。
conditional翻译过来是【有条件的; 条件的; 附带条件的; 依…而定的】意思,就和if else条件判断语句一样,条件成立时,才进行Bean 的注册。
@Conditional是Spring 原生框架提供的注解,需要配置一个Condition接口的实现类:
public @interface Conditional {
Class<? extends Condition>[] value();
}
在Condition接口中,声明了一个匹配方法,返回真假,为真时进行注册,反之不注册:
@FunctionalInterface
public interface Condition {
boolean matches(ConditionContext var1, AnnotatedTypeMetadata var2);
}
案例演示:需要根据一个自定义的配置项来决定是否注册一个Bean 。
首先实现Condition接口,在匹配方法中,查询当前环境中是否配置了某个配置项:
public class MyCondition implements Condition {
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// 获取属性 condition
String condition = context.getEnvironment().getProperty("condition");
return StringUtils.hasText(condition);
}
}
在需要配置该条件类上,添加@Conditional注解:
@Component
@Conditional(MyCondition.class)
public class TestCondition {
}
可以发现,只有在YML 中配置了condition时,才会注册该Bean。
condition: hahaha
也可以自定义一个派生条件注解,使用更加的方便:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional({MyCondition.class})
public @interface ConditionalOnMyProperty {
}
除了写在类上,还可以直接写上方法上:
@Bean
@ConditionalOnMyProperty
TestCondition testCondition(){
return new TestCondition();
}
Spring Boot 已经为我们提供了直接使用的条件注解,并在自动配置源码中大量运用:

接下来挑一些常用的注解进行说明和演示。
在上下文中存在某个Bean 对象时,才会注入当前Bean 。
源码如下:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional({OnBeanCondition.class})
public @interface ConditionalOnBean {
// 条件Bean Class 数组
Class<?>[] value() default {};
// 条件Bean 的类名,Class.getName()
String[] type() default {};
// 使用某个注解的条件Bean
Class<? extends Annotation>[] annotation() default {};
// 条件Bean 的Bean 名称
String[] name() default {};
// 条件Bean 容器层级
// ALL:所有容器
// ANCESTORS:所有祖先容器,但不包含当前容器
// CURRENT:仅当前容器
SearchStrategy search() default SearchStrategy.ALL;
// 条件Bean 的类型为指定类型,并且泛型为当前需要注入的Bean
Class<?>[] parameterizedContainer() default {};
}
在上下文中不存在某个Bean 对象时,才会注入当前Bean ,是@ConditionalOnBean的反例。
配置属性同@ConditionalOnBean:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional({OnBeanCondition.class})
public @interface ConditionalOnMissingBean {
Class<?>[] value() default {};
String[] type() default {};
Class<?>[] ignored() default {};
String[] ignoredType() default {};
Class<? extends Annotation>[] annotation() default {};
String[] name() default {};
SearchStrategy search() default SearchStrategy.ALL;
Class<?>[] parameterizedContainer() default {};
}
某个class存在于类路径上,才会注入当前Bean 。
源码如下:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional({OnClassCondition.class})
public @interface ConditionalOnClass {
// 指定条件类的Class 类型
Class<?>[] value() default {};
// 指定条件类的类名
String[] name() default {};
}
在Spring Boot 自动配置类中,大量使用了该注解,比如引入了MongoDB 客户端包,才会触发MongoDB 的自动配置。

某个class不存在于类路径上,才会注入当前Bean ,是@ConditionalOnClass的反例。
只能配置一个类名属性:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional({OnClassCondition.class})
public @interface ConditionalOnMissingClass {
String[] value() default {};
}
当Web 应用程序和指定的类型一致时,才会注入当前Bean ,如果某些Bean 在指定的Web 程序类型时才能使用,这很有效。
比如当前为SERVLET类型的Web 容器,才会注册TestCondition 。
@Component
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
public class TestCondition {
}
@ConditionalOnNotWebApplication则相反。
存在某个配置属性值时,才会注入当前Bean,可以在配置文件中添加一个开关属性,实现是否加载当前Bean 的功能。
源码如下:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Documented
@Conditional({OnPropertyCondition.class})
public @interface ConditionalOnProperty {
// 对应属性值为真或假,可配置多个,一个为假,都不注册
// 和 name() default {}; 不可同时存在
String[] value() default {};
// 属性名前缀
String prefix() default "";
// 属性完整名称或部分名称,可与prefix组合使用,组成完整的配置属性名称
String[] name() default {};
// 是否有属性值
String havingValue() default "";
// 未配置havingValue的值时,是否加载当前Bean
boolean matchIfMissing() default false;
}
比如我们在配置文件中添加一个配置,用于判定是否开启某个功能:
spring:
h2:
console:
enabled: true
可以使用以下两种方式配置:
@ConditionalOnProperty(value = {"spring.h2.console.enabled"})
@ConditionalOnProperty(
prefix = "spring.h2.console",
name = {"enabled"},
havingValue = "true",
matchIfMissing = false
)
@Profile可以根据当前激活的环境决定是否注册某个Bean。
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional({ProfileCondition.class})
public @interface Profile {
String[] value();
}
这是非常常用的一注解,比如写了一个打印开发日志的切面,切换到测试或者生产环境时,需要关闭,这个时候就可以在切面上添加@Profile,添加一个dev属性,表示只有当spring.profiles.active配置为开发环境时,这个切面才有效。
@Aspect
@Profile({"dev"})
public class RequestLogAspect {
// 切面
}
@ConditionalOnExpression:当SpEL 表达式为true 。
@ConditionalOnJava:当JVM版本为指定的版本范围时。
@ConditionalOnResource:当类路径下有指定的资源时。
@ConditionalOnJndi:在JNDI存在时。
@ConditionalOnSingleCandidate:当指定的Bean在容器中只有一个,或者有多个但是指定了首选的Bean时。