• 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接口

  • 相关阅读:
    jupyter notebook 的快捷键【转】
    jupyter notebook 添加Markdown
    jupyter notebook 更改工作目录
    [转]pycharm的一些快捷键
    Git/github基础 (四) 搭建homepages
    Git/github基础 (三) Issues
    Git/github基础 (二) fork
    Git/github基础 (一)
    《Python基础教程》第2章读书笔记(1)
    jquery1.9学习笔记 之层级选择器(三)
  • 原文地址:https://www.cnblogs.com/strongmore/p/16229644.html
Copyright © 2020-2023  润新知