Spring Boot 注解——@Conditional


简介

@Conditional是Spring4新提供的注解,它的作用是按照一定的条件进行判断,满足条件给容器注册Bean

作用

条件装配,满足Conditional指定的条件,则进行组件注入

根注解与派生注解

image

@Conditional是一个根注解,这个根注解下面派生了许多派生注解

  • @ConditionalOnCloudPlatform
    • 不常用
    • 当指定的云平台处于活动状态时匹配才能注入
  • @ConditionalOnBean
    • 常用
    • 当容器中存在指定的Bean时,在执行指定操作
  • @ConditionalOnMissingBean
    • 常用
    • 当容器中不存在指定的Bean时,才进行指定事情
  • @ConditionalOnWarDeployment
    • 不常用
    • 当应用程序是传统的 WAR 部署时匹配,进行某些事情;对于带有嵌入式服务器的应用程序,此条件将返回 false
  • @Profile
    • 常用
    • 当指定的配置文件处于活动状态的时候(即当前程序使用了这个配置文件),才进行指定事情
    • 与@Configuration一起使用,详情参考Spring Boot 注解——@Configuration
  • @ConditionalOnNotWebApplication
    • 不常用
    • 当应用程序不是 Web 应用程序时,执行某些操作。
  • @ConditionalOnWebApplication
    • 不常用
    • 当应用程序是 Web 应用程序时,执行某些操作。 默认情况下,任何 Web 应用程序都会匹配,但可以使用type()属性缩小范围
  • @ConditionalOnMissingClass
    • 不常用(对于开发框架,应该会比较常用)
    • 仅当指定的类不在类路径上时才匹配。
  • @ConditionalOnClass
    • 不常用(对于开发框架,应该会比较常用)
    • 仅当指定的类在类路径上时才匹配。可以在@Configuration类上安全地指定value()因为在加载类之前使用 ASM 解析注释元数据。 放置在@Bean方法上时需要格外小心,考虑在单独的Configuration类中隔离条件,特别是如果方法的返回类型与target of the condition的target of the condition匹配
    • 简单来说就是,如果指定的类在项目中存在,就会执行指定操作,如果不存在,就不会执行
  • @ConditionalOnExpression
    • 不常用
    • 取决于SpEL表达式值的条件元素的配置注释。
  • @ConditionalOnResource
    • 常用
    • 当且仅当项目中存在某个资源的时候,才执行相关操作
  • @ConditionalOnRepositoryType
    • 不常用
    • 仅在启用特定类型的 Spring Data 存储库时匹配
  • @ConditionalOnProperty
    • 常用
    • 当指定的属性上有特定的值时,才执行相关操作
  • @ConditionalOnJava
    • 不常用
    • 根据运行应用程序的 JVM 版本匹配
  • @ConditionalOnSingleCandidate
    • 常用
    • 仅当指定类的 bean 已包含在BeanFactory并且可以确定单个候选时才匹配。如果BeanFactory中已经包含多个匹配的 bean 实例但已经定义了一个主要候选者,则条件也将匹配; 本质上,如果自动装配具有定义类型的 bean 将成功,则条件匹配。该条件只能匹配到目前为止已由应用程序上下文处理的 bean 定义,因此,强烈建议仅在自动配置类上使用此条件。 如果候选 bean 可能是由另一个自动配置创建的,请确保使用此条件的那个 bean 之后运行
    • 简单理解:表示当指定Bean在容器中只有一个,或者虽然有多个但是指定首选Bean。
  • @ConditionalOnEnabledResourceChain
    • 不常用
    • 检查是否启用了 Spring 资源处理链。 如果ResourceProperties.Chain.getEnabled()为true或webjars-locator-core在类路径上,则webjars-locator-core 。

常用注解源码解析和具体使用方法

@Conditional

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

	/**
	 * 必须匹配才能注册组件的所有Conditions
	 */
	Class<? extends Condition>[] value();
}

Conditional是一个根注解,一系列的判断条件需要自己去实现,根据源码可以看到,@Conditional拥有一个value属性,接收的参数类型是Class数组这个Class类型继承Condition接口,也就是说这个条件判断需要自己编程实现

@FunctionalInterface
public interface Condition {
	/**
	 * 确定条件是否匹配。
	 * @param context  条件上下文
	 * @param metadata 被检查的class或method的元数据
	 * @return true如果条件匹配并且组件可以被注册,或false否决注释组件的注册
	 */
	boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}

自己编写的类,需要继承Condition这个接口,如果matches返回的是true,则注入容器,反之,不注入

示例

  • 普通实体类
public class Person {
	private String name;
	private Integer age;

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public Integer getAge() {
		return age;
	}

	public void setAge(Integer age) {
		this.age = age;
	}

	public Person(String name, Integer age) {
		this.name = name;
		this.age = age;
	}

	@Override
	public String toString() {
		return "Person{" + "name='" + name + '\'' + ", age=" + age + '}';
	}
}
  • 配置类
@Configuration
public class BeanConfig {
	/**
	 * 如果WindowsCondition的实现方法返回true,则注入这个bean
	 */
	@Conditional({WindowsCondition.class})
	@Bean(name = "bill")
	public Person person1(){
		return new Person("Bill Gates",62);
	}

	/**
	 * 如果LinuxCondition的实现方法返回true,则注入这个bean
	 */
	@Conditional({LinuxCondition.class})
	@Bean("linus")
	public Person person2(){
		return new Person("Linus",48);
	}
}
  • 继承Condition接口
public class WindowsCondition implements Condition {
 
    /**
     * @param conditionContext:判断条件能使用的上下文环境
     * @param annotatedTypeMetadata:注解所在位置的注释信息
     * */
    @Override
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
        //获取ioc使用的beanFactory
        ConfigurableListableBeanFactory beanFactory = conditionContext.getBeanFactory();
        //获取类加载器
        ClassLoader classLoader = conditionContext.getClassLoader();
        //获取当前环境信息
        Environment environment = conditionContext.getEnvironment();
        //获取bean定义的注册类
        BeanDefinitionRegistry registry = conditionContext.getRegistry();
 
        //获得当前系统名
        String property = environment.getProperty("os.name");
        //包含Windows则说明是windows系统,返回true
        if (property.contains("Windows")){
            return true;
        }
        return false;
    }
}


public class LinuxCondition implements Condition {
 
    @Override
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
 
        Environment environment = conditionContext.getEnvironment();
 
        String property = environment.getProperty("os.name");
        if (property.contains("Linux")){
            return true;
        }
        return false;
    }
}
  • 测试类
@Test
public void test1(){
  String osName = applicationContext.getEnvironment().getProperty("os.name");
  System.out.println("当前系统为:" + osName);
  Map<String, Person> map = applicationContext.getBeansOfType(Person.class);
  System.out.println(map);
}

运行结果如下:
image

@ConditionalOnBean

源码分析

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
// 使用父注解@Conditional并自行实现Condition接口
@Conditional(OnBeanCondition.class)
public @interface ConditionalOnBean {

	/**
	 * 应该检查的 bean 的类型。 当BeanFactory中包含所有指定类的 bean 时,条件匹配
	 * @return 要检查的 bean 的类型
	 */
	Class<?>[] value() default {};

	/**
	 * 应该检查的 bean 的类型。 当BeanFactory中包含所有指定类的 bean 时,条件匹配
	 * @return @return 要检查的 bean 的类型名称
	 */
	String[] type() default {};

	/**
	 * 装饰应检查的 bean 的注释类型。 当在BeanFactory bean 上定义了所有指定的注释时,条件匹配
	 * @return 要检查的类级注释类型
	 */
	Class<? extends Annotation>[] annotation() default {};

	/**
	 * 要检查的 bean 的名称。 当所有指定的 bean 名称都包含在BeanFactory中时,条件匹配
	 * @return 要检查的 bean 的名称
	 */
	String[] name() default {};

	/**
	 * 决定是否应考虑应用程序上下文层次结构(父上下文)的策略
	 * @return the search strategy
	 */
	SearchStrategy search() default SearchStrategy.ALL;

	/**
	 * 可能在其泛型参数中包含指定 bean 类型的其他类。 
	 * 例如,声明 value = Name.class和 parameterizedContainer = NameRegistration.class
	 * 将检测Name和NameRegistration<Name>。
	 * @return 容器类型
	 * @since 2.1.0
	 */
	Class<?>[] parameterizedContainer() default {};
}

以上就是@ConditionalOnBean注解的具体实现,可以看的出,这个注解实际使用了父注解@Conditional和一个处理类OnBeanCondition,真正的判断逻辑就在这个OnBeanCondition中。
image
首先看这个类头,OnBeanCondition中并没有实现Condition,但是他继承了一个叫FilteringSpringBootCondition的类,在进入这个类看看
image

FilteringSpringBootCondition这个类也没有实现Condition这个类,不过他也继承了一个叫SpringBootsCondition的类

image

进入SpringBootCondition这个类后,终于实现了Condition,实现Condition这个接口,那必定实现这个matches方法,下面我们就详细看一下这个方法

@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);
	}
}

这里看看第7行最重要的逻辑
image

@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
	ConditionMessage matchMessage = ConditionMessage.empty();
	MergedAnnotations annotations = metadata.getAnnotations();
	// 判断是否标注了@ConditionalOnBean
	if (annotations.isPresent(ConditionalOnBean.class)) {
		// 获取注解上的属性和对应的值
		Spec<ConditionalOnBean> spec = new Spec<>(context, metadata, annotations, ConditionalOnBean.class);
		// 在容器中查询,放回是否具有对应注解的类,MatchResult是由多个map和list组成,
		// 是判断容器里面的类,是否有注解上面的标记。
		// 重要逻辑
		MatchResult matchResult = getMatchingBeans(context, spec);
		// 判断是否存在由@ConditionalOnBean标注的类,如果有,返回true,没有返回,false
		if (!matchResult.isAllMatched()) {
			String reason = createOnBeanNoMatchReason(matchResult);
			return ConditionOutcome.noMatch(spec.message().because(reason));
		}
		matchMessage = spec.message(matchMessage)
						.found("bean", "beans")
						.items(Style.QUOTE,matchResult.getNamesOfAllMatches());
	}
	// 判断是否标注了@ConditionalOnSingleCandidate
	if (metadata.isAnnotated(ConditionalOnSingleCandidate.class.getName())) {
		Spec<ConditionalOnSingleCandidate> spec = new SingleCandidateSpec(context, metadata, annotations);
		MatchResult matchResult = getMatchingBeans(context, spec);
		if (!matchResult.isAllMatched()) {
			return ConditionOutcome.noMatch(spec.message().didNotFind("any beans").atAll());
		}
		else if (!hasSingleAutowireCandidate(context.getBeanFactory(), matchResult.getNamesOfAllMatches(),
				spec.getStrategy() == SearchStrategy.ALL)) {
			return ConditionOutcome.noMatch(spec.message().didNotFind("a primary bean from beans")
						.items(Style.QUOTE, matchResult.getNamesOfAllMatches()));
		}
		matchMessage = spec.message(matchMessage)
							.found("a primary bean from beans")
							.items(Style.QUOTE,matchResult.getNamesOfAllMatches());
	}
	// 判断是否标注了@ConditionalOnMissingBean
	if (metadata.isAnnotated(ConditionalOnMissingBean.class.getName())) {
		Spec<ConditionalOnMissingBean> spec = new Spec<>(context, metadata, annotations,
				ConditionalOnMissingBean.class);
		MatchResult matchResult = getMatchingBeans(context, spec);
		if (matchResult.isAnyMatched()) {
			String reason = createOnMissingBeanNoMatchReason(matchResult);
			return ConditionOutcome.noMatch(spec.message().because(reason));
		}
		matchMessage = spec.message(matchMessage).didNotFind("any beans").atAll();
	}
	return ConditionOutcome.match(matchMessage);
}

分析getMatchOutcome可以看到,getMatchOutcome支持@ConditionalOnSingleCandidate@ConditionalOnMissingBean@ConditionalOnBean,也就是说,对于这三个注解的判断逻辑都在这里。其中最重要的逻辑就在getMatchingBeans这个方法,这个方法比较复杂,也比较简单,就是根据当前上下文容器,查找是否存在对应的类。SearchStrategy这个枚举定义了搜索的范围:

  • All搜索整个上下文,父子容器等等
  • ANCESTORS搜索所有祖先,除开当前上下文
  • CURRENT搜索当前上下文。

image

@ConditionalOnProperty

源码解析

@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
@Documented
@Conditional(OnPropertyCondition.class)
public @interface ConditionalOnProperty {

	/**
	 * name的别名
	 * @return the names
	 */
	String[] value() default {};

	/**
	 * 应该应用于每个属性的前缀。
	 * 如果未指定,前缀会自动以点结尾。 
	 * 一个有效的前缀由一个或多个用点分隔的单词定义(例如"acme.system.feature" )
	 * @return 前缀名称
	 */
	String prefix() default "";

	/**
	 * 要测试的属性的名称。如果已定义前缀,则将其应用于计算每个属性的完整键。
	 * 例如,如果前缀是app.config并且一个值是my-value ,则完整键将是app.config.my-value
	 * 使用虚线符号来指定每个属性,即全部小写,并用“-”分隔单词(例如: my-long-property )
	 * @return the names
	 */
	String[] name() default {};

	/**
	 * 可与name组合使用,比较获取到的属性值与havingValue给定的值是否相同,相同才加载配置
	 */
	String havingValue() default "";

	/**
	 * 缺少该配置属性时是否可以加载。如果为true,没有该配置属性时也会正常加载;反之则不会生效
	 */
	boolean matchIfMissing() default false;
}

注解中的属性使用方法说明见上面代码注释
分析@ConditionalOnProperty可知,和其他的一样,也是使用了父注解@Conditional和逻辑判断类OnPropertyCondition,下面看一下这个判断逻辑类的组成

image

该类也是继承了SpringBootCondition这个类,参考@ConditionalOnBean源码,可以知道,SpringBootCondition实现了Condition接口,其中最主要的方法就是getMatchOutcome
OnPropertyCondition中的getMatchOutcome逻辑:

@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
	// 获取ConditionalOnProperty注解属性和对应的值
	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());
		// 如果结果匹配,结果信息存在match,反之存在noMatch中
		(outcome.isMatch() ? match : noMatch).add(outcome.getConditionMessage());
	}
	if (!noMatch.isEmpty()) {
		return ConditionOutcome.noMatch(ConditionMessage.of(noMatch));
	}
	return ConditionOutcome.match(ConditionMessage.of(match));
}

其中最主要的逻辑为determineOutcome,这个方法根据@ConditionalOnProperty中的需要做判断的属性去判断,如果当前属性不存在且不允许缺少属性时加载或属性存在,但值不对应,则认为不匹配,反之,认为匹配。

示例

@Configuration
//在application.properties配置"mf.assert",对应的值为true
@ConditionalOnProperty(prefix="mf",name = "assert", havingValue = "true", matchIfMissing = true)
public class AssertConfig {
    @Autowired
    private HelloServiceProperties helloServiceProperties;
    @Bean
    public HelloService helloService(){
        HelloService helloService = new HelloService();
        helloService.setMsg(helloServiceProperties.getMsg());
        return helloService;
    }
}
  • 当配置文件中存在mf.assert,值为false,该配置类不会注入
  • 当配置文件中不存在mf.assert,且matchIfMissing=true, 该配置类不会注入
  • 当配置文件中存在mf.assert,值为true,则注入该配置类


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM