• 条件式注解@ConfigurationOnXXX原理详解


    现象

    
    @Configuration
    class A{
    }
    
    @Configuration
    class B{
    
        @Bean
        @ConditionalOnBean(A.class)
        public C getC(){
            return new C();
        }
    }
    
    class C{
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    我们通常能看到类似如上的代码:在注入Bean C的时候,加了一个注解:@ConditionalOnBean(A.class),这个注解想必大家也都明白,就是必须A.class也被注入.其实类似的注解有很多,作用和原理都是差不多的,但是原理是什么呢?下面就以@ConditionalOnBean为主看下:

    @Target({ ElementType.TYPE, ElementType.METHOD })
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Conditional(OnBeanCondition.class)
    public @interface ConditionalOnBean {
    // 省略内容,不是关键
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在注解@ConditionalOnBean的定义中,可以看到上面有个@Conditional注解,作为元注解使用,参数是OnBeanCondition类,下面再看下@Conditional

    @Target({ElementType.TYPE, ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface Conditional {
    
    	/**
    	 * All {@link Condition} classes that must {@linkplain Condition#matches match}
    	 * in order for the component to be registered.
    	 */
    	Class<? extends Condition>[] value();
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    我们可以看到注解@Conditional的value的类型有泛型约定必须是Condition类型的子类,很显然OnBeanCondition就是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);
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    可以看到这是一个函数式接口意思也就是说,这里面的matches方法的返回结果,是个布尔值,如果是true,那么就满足条件,false自然就不能满足条件,也就是说子类需要实现matches方法,编写自己的判断逻辑,结合我们上面的例子,就是说如果OnBeanCondition的matches方法按照自己的判断逻辑,如果返回的是true,那么Bean C就可以被注入,否则则不能.
    我们这里看下OnBeanCondition类:

    class OnBeanCondition extends FilteringSpringBootCondition implements ConfigurationCondition {
    
    }
    
    • 1
    • 2
    • 3

    这里的ConfigurationCondition 是继承了上面的Condition,下面看下ConfigurationCondition内容:

    public interface ConfigurationCondition extends Condition {
    
    	/**
    	 * Return the {@link ConfigurationPhase} in which the condition should be evaluated.
    	 */
    	ConfigurationPhase getConfigurationPhase();
    
    	/**
    	 * The various configuration phases where the condition could be evaluated.
    	 */
    	enum ConfigurationPhase {
    
    		/**
    		 * The {@link Condition} should be evaluated as a {@code @Configuration}
    		 * class is being parsed.
    		 * 

    If the condition does not match at this point, the {@code @Configuration} * class will not be added. */ PARSE_CONFIGURATION, /** * The {@link Condition} should be evaluated when adding a regular * (non {@code @Configuration}) bean. The condition will not prevent * {@code @Configuration} classes from being added. *

    At the time that the condition is evaluated, all {@code @Configuration} * classes will have been parsed. */ REGISTER_BEAN } }

    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    这个接口是SpringBoot中控制bean能否注入到容器中的核心接口.这个接口相比于Condition,其实就是多了个枚举,这里面的枚举值,是用来控制配置类的解析时机控制.

    	enum ConfigurationPhase {
        /**
         * 这个枚举值就是用来控制校验配置类(被@Configuration注解)的解析时做校验的,什么样的场景才会用呢?就比如一个配置类里面有很多配置bean要被注入,
         * 但是你希望通过某个条件作为前置条件,如果此时matches返回对的结果是false,那么配置类里面的bean都不会再被解析.
         **/
    		PARSE_CONFIGURATION,
    
        /**
         * 这个枚举值就是用来控制Bean(非@Configuration)的注入,如果matches函数返回的是true,则Bean类可以注入成功,
         * 如果是false,则整个Bean类就不会再进行解析注入到容器了
         **/
    		REGISTER_BEAN
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    StackOverflow相关解释ConfigurationPhase Two Phase
    下面再讲一下@ConditionalOnBean、@ConditionalOnClass等注解是如何起作用的:
    其实就是这些注解定义的元注解@Conditional起得作用,springboot只通过ConditionEvaluator来处理@Conditional注解, @Conditional(OnBeanCondition.class)里面不同的 Condition实现类,其实就是对应不同的条件注解的解析生效.就比如@ConditionalOnClass的定义里面元注解@Conditional(OnClassCondition.class)的入参就是OnClassCondition类,只不过这个类实现了自己的判断规则,也就是自己实现了Condition接口的matches方法.

  • 相关阅读:
    处理告警“ warning #69-D integer conversion resulted in truncation”的方法
    【Linux】写一个日志类
    JAVA泛型及元组
    如何理解低代码开发工具?
    【Redis】Redis整合SSM&&Redis注解式缓存&&Redis中的缓存穿透、雪崩、击穿的原因以及解决方案(详解)
    c语言练习71: 找出数组的最⼤公约数
    【教学类-38-02】20230724京剧脸谱2.0——竖版(小彩图 大面具)(Python 彩图彩照转素描线描稿)
    麒麟KYLINOS桌面操作系统2303上安装tigervnc
    go初识iris框架(六) - session使用和控制
    C++内存管理
  • 原文地址:https://blog.csdn.net/qq_15022971/article/details/126343092