当前位置 博文首页 > 一颗小陨石的博客:【SpringBoot】ConditionXX的作用与原理以及

    一颗小陨石的博客:【SpringBoot】ConditionXX的作用与原理以及

    作者:[db:作者] 时间:2021-09-16 13:41

    在这里插入图片描述

    在前面一文中,我们讲了SpringBoot启动的几个阶段和自动装配的原理,我们会发现,在Spring中,使用了大量的@ConditionXXX注解,本文就来介绍下@Condition注解的原理。

    问题:

    1. ConditionXX注解是做什么的?如何使用?
    2. 如何自定义Condition?
    3. Condition实现原理是什么?

    一、@ConditionXXX注解的作用

    在这里插入图片描述

    常见的一些@Condition相关的注解如上。

    该类注解的作用就是在满足某个条件的时候才将某个类进行实例化并加入到Spring容器中来。

    如:

    • @ConditionalOnBean:当容器中有某个Bean的时候才调用当前被修饰的方法、或类,并加入到容器管理

      @Configuration
      public class MyConfiguration {
      
          @Bean(name = "haha")
          public People people(){
              return new People();
          }
      
          @Bean
          @ConditionalOnBean(name = "haha")
          public  People onPeople(){
              System.out.println("上下文有名为haha的bean");
              return new People();
          }
      
      }
      

      在这里插入图片描述

      启动时就加载到了onPeople方法。如果把上面注入的名为haha的bean去掉,则当前方法就不会被调用。

    • ConditionalOnClass:当工程中有某个类时才调用

       @Bean
          @ConditionalOnClass(name = "com.wml.config.User")
          public People onClass(){
              System.out.println("有User类");
              return new People();
          }
      
    • 同样的还有对应的Missing注解,与上面两个相反:

      @Bean("missingPeople")
          @ConditionalOnMissingClass(value = "com.wml.pojo.Test")
          public People onMissingClass(){
              System.out.println("上下文没有Test类");
              return new People();
          }
      
          @Bean
          @ConditionalOnMissingBean(name = "didi")
          public  People missingPeople(){
              System.out.println("上下文无名为didi的bean");
              return new People();
          }
      

      在没有对应的类和Bean的时候才生效。

    • ConditionalOnExpression:表达式成立才生效

      		@Bean
          @ConditionalOnExpression(value = "${server.port}==8080")
          public People onExpression(){
              System.out.println("端口为8080");
              return new People();
          }
      

      在这里插入图片描述

    • @ConditionalOnProperty:配置文件的属性匹配时才生效:

      		@Bean
          @ConditionalOnProperty(prefix = "spring.application",name = "name",havingValue = "test-application")
          public People prop(){
              System.out.println("应用名为test-application");
              return new People();
          }
      

      在这里插入图片描述

    暂且列举这么多。

    二、自定义Condition注解

    观察上面一些Condition注解,以ConditionalOnMissingBean为例:

    @Conditional(OnBeanCondition.class)
    public @interface ConditionalOnMissingBean {
    }
    

    其上都使用了一个@Conditional(OnBeanCondition.class)注解,当括号中的类返回true时才会生效。而里面的类,跟进去,最终都是实现了@Condition接口:

    @FunctionalInterface
    public interface Condition {
    
    	boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
    
    }
    

    而我们要实现自己的Condition类,就需要实现该接口,并在matches方法中定义匹配规则。

    如:

    public class MyCondition implements Condition {
        @Override
        public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
          	String age=context.getEnvironment().getProperty("my.age");
            return age != null && (Integer.valueOf(age) == 22);
        }
    }
    

    获取配置文件中的my.age属性,如果没配置或者值不是22,则返回false,否则返回true。

    my:
      age: 22
    
     @Bean
        @Conditional(MyCondition.class)
        public People cusCondition(){
            System.out.println("自定义Condition生效");
            return new People();
        }
    

    在这里插入图片描述

    可以看到自定义的Condition生效了。

    而如果改成其他的年龄或不写,则不会生效。

    三、Condition原理

    在看源码前先猜测下可能是如何实现的?

    首先,处理@Configuration、@Import等注解,都是在前面说的那个ConfigurationClassPostProcessor类中进行集中处理的,而我们的Condition注解,那么可能就需要判断某个类上是否有该条件注解,如果有的话,那么肯定会去拿它的条件,如果条件满足,则正常实例化,如果不满足,则不进行实例化。

    那么进来具体看看:

    具体处理是在这个类的parser.parse方法里处理:

    一直跟进去,配置类会在下面这个方法处理:

    protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
    		if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
    			return;
    		}
      .............
    
    }
    
    

    在方法第一行,首先调用了一个shouldSkip方法,该方法就是用来判断当前类是否需要跳过,如果需要跳过则不装载到容器中,否则正常装载。

    public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) {
       //1.如果没有被Conditional注解,则直接返回false,不需要跳过
    		if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {
    			return false;
    		}
    
    		......
    
    		List<Condition> conditions = new ArrayList<>();
       //2.收集当前类中所有Conditional注解的value值,如我们自定义的MyCondition类名,并通过getCondition获取对应的Condition(实现类),即我们的MyCondition类,加入到conditions集合中
    		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方法,如果匹配就返回false,不匹配就返回true,不需要加载到容器中
    			if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) {
    				return true;
    			}
    		}
    
    		return false;
    	}
    

    但是可以看到matches实现类只有下面四个:

    在这里插入图片描述

    这里只看SpringBootCondition,为啥呢?因为那几个Condition类都继承了该抽象类。

    @ConditionalOnClass的条件类OnClassCondition:

    在这里插入图片描述

    来到SpringBootConditionmatches方法:

    @Override
    public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
       String classOrMethodName = getClassOrMethodName(metadata);
       try {
          // 钩子方法,调到用具体子类中方法。ConditionOutcome 这个类里面包装了是否需
          // 跳过和打印的日志
          ConditionOutcome outcome = getMatchOutcome(context, metadata);
          logOutcome(classOrMethodName, outcome);
          recordEvaluation(context, classOrMethodName, outcome);
          return outcome.isMatch();
       }
       ..............
    }
    

    其中ConditionOutcome中有一个布尔类型的match字段,该字段就是匹配的结果。这里的getMatchOutcome是一个模板方法,实现交给了具体的子类,我们就以OnClassCondition为例看下:

    	@Override
    	public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
    		ClassLoader classLoader = context.getClassLoader();
    		ConditionMessage matchMessage = ConditionMessage.empty();
    		List<String> onClasses = getCandidates(metadata, ConditionalOnClass.class);
    		if (onClasses != null) {