@Conditional学习
概要
在构建Spring Boot应用程序时,我们有时希望仅在满足某些条件时才将bean或模块加载到应用程序上下文中,可以在测试期间禁用某些bean,也可以对运行时环境中的某个属性做出反应。
Spring 应用程序上下文包含了应用程序在运行时需要的所有Bean对象,Spring 4.0引入了@Conditional注释,它的主要作用就是根据条件给容器注入bean,简称条件注解。@Conditional注释允许我们定义自定义条件,以适用于应用程序上下文的某些部分。
Spring Boot在此基础上构建,并提供了一些预定义的条件(在org.springframework.boot.autoconfigure.condition
包下),供我们使用。
在本教程中,我们将使用一些用例来解释为什么我们需要有条件加载的bean。然后,我们将看到如何应用条件以及Spring Boot提供哪些条件。我们还将实现一个自定义条件。
@Conditional注解
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
/**
* All {@link Condition Conditions} that must {@linkplain Condition#matches match}
* in order for the component to be registered.
*/
Class<? extends Condition>[] value();
}
-
@Conditional注解可以放在类上,Spring 自动扫描的一切类 (@Configuration, @Component, @Service, @Repository, or @Controller) 都可以通过添加相应的@Conditional注解进行条件注入。
@Conditional(xxx) @Service public class TestService { }
-
@Conditional注解可以放在方法上,所以有 @Bean 标记的方法也可以使用@Conditional注解进行条件注入。
@Configuration public class AppConfig { @Conditional(xxx) @Bean public User user() { return new User(); } }
-
@Conditional 注解接收的参数是
extends Condition
接口的泛型类,也就是说,我们要使用 @Conditional 注解,只需要实现 Condition 接口并重写其方法即可。
Condition接口
@FunctionalInterface
public interface Condition {
/**
* Determine if the condition matches.
* @param context the condition context
* @param metadata the metadata of the {@link org.springframework.core.type.AnnotationMetadata class}
* or {@link org.springframework.core.type.MethodMetadata method} being checked
* @return {@code true} if the condition matches and the component can be registered,
* or {@code false} to veto the annotated component's registration
*/
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
- 接口的 matches 方法返回的是 boolean 类型,用来判断是否满足指定条件。
- context:条件上下文
- Metadata:类或者方法的元数据对象
@Conditional使用
- 定义Bean对象
import lombok.Data;
@Data
public class User {
}
- 自定义 Condition,实现 Condition 接口
import java.time.DayOfWeek;
import java.time.LocalDate;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
/**
* 如果是周六或周日,注入Bean对象
* 如果是工作日,不注入Bean对象
*/
public class IsWeekendDayCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
DayOfWeek dayOfWeek = LocalDate.now().getDayOfWeek();
return DayOfWeek.SUNDAY == dayOfWeek || DayOfWeek.SATURDAY == dayOfWeek;
}
}
- 自定义配置类,用于配置Bean的注入
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import com.huat.wl.condition.condition.IsWeekendDayCondition;
import com.huat.wl.condition.domain.User;
@Configuration
public class BeanConfig {
@Bean
@Conditional(IsWeekendDayCondition.class)
public User user() {
return new User();
}
}
Condition工作原理
核心方法:ConditionEvaluator#shouldSkip
ConditionEvaluator#shouldSkip,判定基于@Conditional注解的配置类是否应该忽略,返回true,忽略配置。
主要步骤:
1、获取所有的Condition条件对象
2、排序满足优先顺序
3、根据处理好的Condition条件对象集合,判断是否符合条件注册。只要某个不符合,直接返回true,即忽略该配置。
/**
* Determine if an item should be skipped based on {@code @Conditional} annotations.
* @param metadata the meta data
* @param phase the phase of the call
* @return if the item should be skipped
*/
public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) {
// 1.判断配置类是否存在注解,是否有@Condition注解,没有@Conditional 加载该配置
if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {
return false;
}
// 如果注册过程为空
if (phase == null) {
// 如果配置类有注解标注,并且注解类型为@Configuration或@Bean等注解标注
if (metadata instanceof AnnotationMetadata &&
ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) {
return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION);
}
return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN);
}
List<Condition> conditions = new ArrayList<>();
// 获取@Conditional注解的value值,即条件判定类(Condition接口的实现类)
for (String[] conditionClasses : getConditionClasses(metadata)) {
for (String conditionClass : conditionClasses) {
Condition condition = getCondition(conditionClass, this.context.getClassLoader());
conditions.add(condition);
}
}
// 排个序
AnnotationAwareOrderComparator.sort(conditions);
for (Condition condition : conditions) {
ConfigurationPhase requiredPhase = null;
if (condition instanceof ConfigurationCondition) {
requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();
}
// 调用具体条件判定类的matches方法判定是否匹配
if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) {
return true;
}
}
return false;
}
参考:https://www.cnblogs.com/xiaoxing/p/13026423.html
Spring Boot预定义的@Conditional
SpringBootCondition
- SpringBoot条件注解的基类,是对spring的Condition进行拓展,主要增加:
- 1、日志处理
- 2、条件注册的详细报告
- 3、拓展@Conditional提供各式条件注册支持(重点),例如:@ConditionalOnBean @ConditionalOnProperty等
public abstract class SpringBootCondition implements Condition {
private final Log logger = LogFactory.getLog(getClass());
@Override
public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// 获取类名/方法名
String classOrMethodName = getClassOrMethodName(metadata);
try {
// 具体抽象方法,具体子类需要实现该方法进行条件判定
ConditionOutcome outcome = getMatchOutcome(context, metadata);
// 日志输出,记录条件匹配信息
logOutcome(classOrMethodName, outcome);
// 报告记录条件匹配信息
recordEvaluation(context, classOrMethodName, outcome);
// 返回是否匹配
return outcome.isMatch();
}
catch (NoClassDefFoundError ex) {
throw new IllegalStateException("Could not evaluate condition on " + classOrMethodName + " due to "
+ ex.getMessage() + " not found. Make sure your own configuration does not rely on "
+ "that class. This can also happen if you are "
+ "@ComponentScanning a springframework package (e.g. if you "
+ "put a @ComponentScan in the default package by mistake)", ex);
}
catch (RuntimeException ex) {
throw new IllegalStateException("Error processing condition on " + getName(metadata), ex);
}
}
private static String getClassOrMethodName(AnnotatedTypeMetadata metadata) {
if (metadata instanceof ClassMetadata) {
ClassMetadata classMetadata = (ClassMetadata) metadata;
return classMetadata.getClassName();
}
MethodMetadata methodMetadata = (MethodMetadata) metadata;
return methodMetadata.getDeclaringClassName() + "#" + methodMetadata.getMethodName();
}
/**
* Determine the outcome of the match along with suitable log output.
* @param context the condition context
* @param metadata the annotation metadata
* @return the condition outcome
*/
public abstract ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata);
// 省略部分方法
}
@ConditionalOnProperty
- 判断条件:应用环境中的屬性是否存在
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
@Documented
@Conditional(OnPropertyCondition.class)
public @interface ConditionalOnProperty {
// 数组,获取对应property名称的值,与name不可同时使用
String[] value() default {};
// 配置属性名称的前缀,比如spring.http.encoding
String prefix() default "";
// 数组,配置属性完整名称或部分名称
// 可与prefix组合使用,组成完整的配置属性名称,与value不可同时使用
String[] name() default {};
// 可与name组合使用,比较获取到的属性值与havingValue给定的值是否相同,相同才加载配置
String havingValue() default "";
// 缺少该配置属性时是否可以加载。如果为true,没有该配置属性时也会正常加载;反之则不会生效
boolean matchIfMissing() default false;
}
- 条件判定类:OnPropertyCondition
@Order(Ordered.HIGHEST_PRECEDENCE + 40)
class OnPropertyCondition extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
List<AnnotationAttributes> allAnnotationAttributes = annotationAttributesFromMultiValueMap(
metadata.getAllAnnotationAttributes(ConditionalOnProperty.class.getName()));
List<ConditionMessage> noMatch = new ArrayList<>();
List<ConditionMessage> match = new ArrayList<>();
for (AnnotationAttributes annotationAttributes : allAnnotationAttributes) {
ConditionOutcome outcome = determineOutcome(annotationAttributes, context.getEnvironment());
(outcome.isMatch() ? match : noMatch).add(outcome.getConditionMessage());
}
if (!noMatch.isEmpty()) {
return ConditionOutcome.noMatch(ConditionMessage.of(noMatch));
}
return ConditionOutcome.match(ConditionMessage.of(match));
}
}
参考
https://www.cnblogs.com/xiaoxing/p/13026423.html
https://fangjian0423.github.io/2017/05/16/springboot-condition-annotation/