Spring源码分析之@Conditional注解处理

前言

Spring提供了@Conditional注解在自动扫描Bean时可以根据条件来判断是否注册BeanDefinition。

简单使用

看一下@Conditional注解的声明,Spring4.0版本才提供。

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

	/**
	 * 配置Condition接口实现类,可以有多个
	 */
	Class<? extends Condition>[] value();
}
import java.util.Date;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ConfigurationClassPostProcessor;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.util.ClassUtils;

public class TestConditional2 {

  public static void main(String[] args) {
    DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
    ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(
        beanFactory, true);
    String packageName = ClassUtils.getPackageName(TestConditional2.class);
    //扫描
    scanner.scan(packageName);
    new ConfigurationClassPostProcessor().postProcessBeanDefinitionRegistry(beanFactory);
    System.out.println(beanFactory.containsBeanDefinition("beanConfig"));
    System.out.println(beanFactory.containsBeanDefinition("myDate"));
  }

  @Configuration("beanConfig")
  @Conditional(BeanConfigCondition.class)
  public static class BeanConfig {

    @Bean("myDate")
    @Conditional(BeanCondition.class)
    public Date date() {
      return new Date();
    }

  }

  public static class BeanConfigCondition implements Condition {

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
      //ConditionContext容器上下文,当前Class上下文
      return true;
    }

  }

  public static class BeanCondition implements Condition {

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
      //ConditionContext容器上下文,当前Class上下文
      return false;
    }

  }
}

使用ClassPathBeanDefinitionScanner作为扫描器用来扫描@Component注解标记的Class,创建BeanDefinition并注册到BeanFactory中。
ConfigurationClassPostProcessor用来处理@Configuration注解,解析出其中包含@Bean的方法,创建BeanDefinition并注册到BeanFactory中。
在注册时发现Class或者Method包含Conditional注解,会创建配置的Condition实现类对象,根据matches()方法来决定是否注册当前BeanDefinition。

关于ConfigurationCondition接口的使用

Spring在Condition接口之外还提供了ConfigurationCondition,看一下接口声明,它是Condition的子接口

public interface ConfigurationCondition extends Condition {

	/**
	 * 条件被计算的阶段
	 */
	ConfigurationPhase getConfigurationPhase();


	/**
	 * 条件被计算的阶段
	 */
	enum ConfigurationPhase {

		/**
		 * 解析@Configuration类之前,如果条件不满足,@Configuration类不会被加载
		 */
		PARSE_CONFIGURATION,

		/**
		 * 解析所有@Configuration类之后,如果此时条件还不满足,移除已经加载的@Configuration类
		 */
		REGISTER_BEAN
	}

}

看注释可能不是太理解,下面写一个例子

import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ConfigurationClassPostProcessor;
import org.springframework.context.annotation.ConfigurationCondition;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.util.ClassUtils;

public class TestConfigurationCondition {

  public static void main(String[] args) {
    DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
    ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(
        beanFactory, true);
    String packageName = ClassUtils.getPackageName(TestConfigurationCondition.class);
    //扫描
    scanner.scan(packageName);
    new ConfigurationClassPostProcessor().postProcessBeanDefinitionRegistry(beanFactory);
    System.out.println(beanFactory.containsBeanDefinition("beanB"));
    System.out.println(beanFactory.containsBeanDefinition("beanA"));
  }

  @Configuration("beanA")
  @Order(Ordered.HIGHEST_PRECEDENCE + 1)
  public static class BeanA {

  }

  @Configuration("beanB")
  @Conditional(IfBeanAExistsConfigurationCondition.class)
  @Order(Ordered.HIGHEST_PRECEDENCE)
  public static class BeanB {

  }

  public static class IfBeanAExistsCondition implements Condition {

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
      return context.getRegistry().containsBeanDefinition("beanA");
    }
  }

  public static class IfBeanAExistsConfigurationCondition implements ConfigurationCondition {

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
      return context.getRegistry().containsBeanDefinition("beanA");
    }

    @Override
    public ConfigurationPhase getConfigurationPhase() {
      return ConfigurationPhase.REGISTER_BEAN;
    }
  }

}

使用@Order注解配置优先级(值越小优先级越高),保证BeanB在BeanA之前加载,BeanB的加载条件是BeanA在容器中存在。
如果使用IfBeanAExistsCondition,加载BeanB时BeanA还没有加载,所以matches()方法返回false,BeanB不会注册到容器中。
如果使用IfBeanAExistsConfigurationCondition,加载BeanB时先不管条件,等所有Configuration类都加载完了,此时判断条件,因为BeanA已经加载到容器中了,所以BeanB也可以加载。

源码分析

我们在Spring源码分析之@Component注解自动扫描的处理的基础上继续分析Spring对@Conditional注解的处理。
进入ClassPathScanningCandidateComponentProvider类。

protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
                //符合此条件就排除,类似黑名单
		for (TypeFilter tf : this.excludeFilters) {
			if (tf.match(metadataReader, getMetadataReaderFactory())) {
				return false;
			}
		}
                //符合此条件就包含,类似白名单
		for (TypeFilter tf : this.includeFilters) {
			if (tf.match(metadataReader, getMetadataReaderFactory())) {
                                //处理@Conditional注解
				return isConditionMatch(metadataReader);
			}
		}
		return false;
	}

满足包含条件之后,还要检查@Conditional注解,继续isConditionMatch()方法

private boolean isConditionMatch(MetadataReader metadataReader) {
		if (this.conditionEvaluator == null) {
			this.conditionEvaluator =
					new ConditionEvaluator(getRegistry(), this.environment, this.resourcePatternResolver);
		}
		return !this.conditionEvaluator.shouldSkip(metadataReader.getAnnotationMetadata());
	}

创建一个ConditionEvaluator,可以看做一个条件解析器,使用它来判断是否能够注册,shouldSkip()方法返回true,表示应该跳过(不能注册)

public boolean shouldSkip(AnnotatedTypeMetadata metadata) {
		return shouldSkip(metadata, null);
	}

核心方法

public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) {
                //如果没有被@Conditional注解标记,直接返回false,表示不跳过
		if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {
			return false;
		}

		if (phase == null) {
                        //根据metadata是否是配置类,来判断当前是什么阶段(phase)
			if (metadata instanceof AnnotationMetadata &&
					ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) {
                                //Component注解,ComponentScan注解,Import注解,ImportResource注解,如果包含4个注解中的一个,就看做配置类
				return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION);
			}
			return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN);
		}

		List<Condition> conditions = new ArrayList<>();
                //解析出@Conditional注解配置的Class,就是Condition接口的实现类,可以配置多个
		for (String[] conditionClasses : getConditionClasses(metadata)) {
			for (String conditionClass : conditionClasses) {
                                //根据Class创建Condition对象
				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();
			}
                        //如果condition不是ConfigurationCondition接口类型,直接调用matches()方法判断
                        //如果是ConfigurationCondition接口类型,接口返回的阶段和当前阶段不一致,直接false,如果一致,再调用matches()方法判断
			if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) {
				return true;
			}
		}

		return false;
	}

@Conditional注解可以配置多个Class,也是可以配置多个@Conditional注解的,像下面这样,@ConditionalOnClass是SpringBoot提供的一个注解,包含@Conditional注解

@Configuration("beanA")
@ConditionalOnClass({BeanA.class, BeanB.class})
@Order(Ordered.HIGHEST_PRECEDENCE + 1)
public static class BeanA {
}

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

所以上面的getConditionClasses()方法返回的是一个二维数组结构。

分析总结

主要是引入了ConfigurationPhase(阶段)的概念,如果调用shouldSkip()方法时没有传入ConfigurationPhase参数,就会根据当前Class是否为配置类来确定ConfigurationPhase。
如果condition类型不是ConfigurationCondition类型,那么ConfigurationPhase这个参数也就没有作用。
如果condition类型是ConfigurationCondition类型,如果接口返回的阶段和当前方法的参数(阶段)不一致,直接false(表示不跳过),如果一致,再调用matches()方法判断。

参考

一文了解@Conditional注解说明和使用
一文了解ConfigurationConditon接口

posted @ 2022-05-09 20:46  strongmore  阅读(185)  评论(0编辑  收藏  举报