@Conditional学习

@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/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值