• Spring Framework 条件装配 之 @Conditional


    Spring Framework 条件装配 之 @Conditional

    前言

    了解SpringBoot的小伙伴对Conditional注解一定不会陌生,在SpringBoot项目中,Conditional注解被广泛的使用以及扩展出了许多Condition派生注解。虽然Conditional在SpringBoot中被丰富了很多,但它是在Spring Framework 4.0中提出的,所以本文还是以Spring Framework 为基础进行讲解。

    推荐阅读
    黑色的眼睛 の 个人博客
    Spring Framework 组件注册 之 FactoryBean
    Spring Framework 组件注册 之 @Import
    Spring Framework 组件注册 之 @Component

    @Conditional 说明

    要使用@Conditional注解,必须先了解一下Conditiona接口,它与@Conditional注解配合使用,通过源码我们也可以看出,使用@Conditional注解必须要指定实现Conditiona接口的class。

    @Target({ElementType.TYPE, ElementType.METHOD})
    public @interface Conditional {
    	/**
    	 * All {@link Condition Conditions} that must {@linkplain Condition#matches match}
    	 * in order for the component to be registered.
    	 */
    	Class<? extends Condition>[] value();
    }
    

    Conditiona接口中,只定义了一个方法matches,spring在注册组件时,也正是根据此方法的返回值TRUE/FALSE来决定是否将组件注册到spring容器中

    @FunctionalInterface
    public interface Condition {
    	/**
    	 * Determine if the condition matches.
    	 * @param context 条件判断的上下文环境
    	 * @param metadata 正在检查的类或方法的注解元数据
    	 */
    	boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
    }
    

    matches中我们可以获取到ConditionContext接口,根据此接口对象可以获取BeanDefinitionRegistryConfigurableListableBeanFactory等重要对象信息,根据这些对象就可以获取和检查spring容器初始化时所包含的所有信息,再结合业务需求,就可以实现组件注册时的自定义条件判断。

    @Conditional 使用

    首先定义两个普通的JavaBean类

    @Data
    public class Test {
        private String id = "@Bean";
    }
    
    @Data
    public class Test2 {
        private String id = "@Conditional";
    }
    

    通过配置类和@Bean注解,向spring容器中注册组件

    /**
     * spring组件配置类
     */
    @Configuration
    public class TestConfiguration {
        /**
         * 向spring容器中注册Test 类型下beanName为test的组件
         */
        @Bean
        public Test test() {
            return new Test();
        }
    
        /**
         * 根据TestCondition接口的条件判断向spring容器中注册Test2组件
         */
        @Bean
        @Conditional(TestCondition.class)
        public Test2 test2() {
            return new Test2();
        }
    }
    

    自定义实现Condition接口

    public class TestCondition implements Condition {
        /**
         * 当IOC容器中包含 Test类的bean定义信息时,条件成立
         *
         * @param context  条件判断的上下文环境
         * @param metadata 正在检查的类或方法的元数据
         * @return 条件是否成立
         */
        public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
            ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
            String[] testBeanNames = beanFactory.getBeanNamesForType(Test.class);
            return ArrayUtils.isNotEmpty(testBeanNames);
        }
    }
    

    添加spring容器启动引导类

    /**
     * spring 容器启动引导类,测试 @Conditional 功能
     */
    @ComponentScan("com.spring.study.ioc.condition")
    public class TestConditionalBootstrap {
        
        public static void main(String[] args) {
            AnnotationConfigApplicationContext applicationContext =
                    new AnnotationConfigApplicationContext(TestConditionalBootstrap.class);
            String[] names = applicationContext.getBeanNamesForType(Test.class);
            System.out.println("---Test bean names : " + Arrays.asList(names));
            names = applicationContext.getBeanNamesForType(Test2.class);
            System.out.println("---Test2 bean names : " + Arrays.asList(names));
    
            applicationContext.close();
        }
    }
    

    运行spring引导类,控制台打印结果:

    ---hasTestBean : true
    ---Test bean names : [test]
    ---Test2 bean names : [test2]

    由结果可以看出,Test正常注册到了spring容器中,满足了TestCondition接口的条件,所有Test2 也被注册到了spring容器中,为了进一步验证结果,我们将Test组件删除掉,仅保留Test2 的注册,修改配置类如下

    /**
     * spring组件配置类,将Test组件删除掉
     */
    @Configuration
    public class TestConfiguration {
        /**
         * 根据TestCondition接口的条件判断向spring容器中注册Test2组件
         */
        @Bean
        @Conditional(TestCondition.class)
        public Test2 test2() {
            return new Test2();
        }
    }
    

    重新运行spring引导类,控制台打印结果如下:

    ---hasTestBean : false
    ---Test bean names : []
    ---Test2 bean names : []

    由此可见,当Test类在spring容器中没有注册时,不满足TestCondition接口条件,所以Test2 组件也不会被注册到spring容器中。此时如果将test2()注册组件上的@Conditional组件删除,Test2组件又会被正常注册到spring容器中。

    上面的例子中是将@Conditional注解添加到了方法上此时条件仅对当前方法生效,@Conditional注解也可以加在上,此时条件对整个类中的组件注册均生效。按照上面的案例,做出以下调整:

    • TestCondition需要实现ConfigurationCondition接口,用来对配置类做处理

    当配置类上添加了@Conditional注解时,需要注意的是,Condition接口中的条件是控制配置类本身还是控制配置类中的所有组件,因此Spring Framework提供了ConfigurationCondition接口,并使用枚举值让我们自定义选择。

    enum ConfigurationPhase {
    
    	/**
    	 * Condition接口中的条件控制着配置类本身的注册,当条件不匹配时,不会添加@configuration类
    	 */
    	PARSE_CONFIGURATION,
    
    	/**
    	 * 控制Condition接口中的条件是对配置类中的组件进行解析,不会影响配置类本身的注册
    	 */
    	REGISTER_BEAN
    }
    
    • TestCondition接口实现修改如下
    public class TestCondition implements ConfigurationCondition {
        /**
         * 当IOC容器中包含 Test的bean定义信息时,条件成立
         *
         * @param context  条件判断的上下文环境
         * @param metadata 正在检查的类或方法的元数据
         * @return 条件是否成立
         */
        public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
            ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
            String[] testBeanNames = beanFactory.getBeanNamesForType(Test.class);
            boolean hasTestBean = ArrayUtils.isNotEmpty(testBeanNames);
            System.out.println("---hasTestBean : " + hasTestBean);
            return hasTestBean;
        }
    
        public ConfigurationPhase getConfigurationPhase() {
            return ConfigurationPhase.REGISTER_BEAN;
        }
    }
    
    • 对javaBean类和配置类进行调整
    @Data
    @Component // 通过@Component注解直接注册 Test 组件
    public class Test {
        private String id = "@Bean";
    }
    
    @Data
    public class Test2 {
        private String id = "Test2: @Conditional";
    }
    
    @Data
    public class Test3 {
        private String id = "Test3: @Conditional";
    }
    
    • 配置类调整如下,在配置类上添加@Conditional注解
    /**
     * spring组件配置类,根据TestCondition接口的条件判断向spring容器中注册Test2,Test3组件
     */
    @Configuration
    @Conditional(TestCondition.class)
    public class TestConfiguration {
        @Bean
        public Test3 test3() {
            return new Test3();
        }
    
        @Bean
        public Test2 test2() {
            return new Test2();
        }
    }
    
    • 引导类中添加对Test3类型的查询
    String[] names = applicationContext.getBeanNamesForType(Test3.class);
    System.out.println("---Test3 bean names : " + Arrays.asList(names));
    

    启动引导类,控制台打印结果如下:

    ---hasTestBean : true
    ---Test bean names : [test]
    ---Test2 bean names : [test2]
    ---Test3 bean names : [test3]

    由此可见,当TestCondition接口条件匹配时,Test2,Test3均被注册到spring容器中,如果将Test组件不进行注册,我们看看下面的结果。

    • Test类上的@Component注解删除,其余代码均不变
    @Data
    public class Test {
        private String id = "@Bean";
    }
    

    重新启动引导类,打印结果如下:

    ---hasTestBean : false
    ---Test bean names : []
    ---Test2 bean names : []
    ---Test3 bean names : []

    由此可以看出,TestCondition的条件控制着配置类中的组件注册

    使用 @Conditional 的注意点

    • @Conditional注解加在方法上时,可以直接使用Condition接口进行实现,通过条件匹配,判断组件是否可以被注册到spring容器中
    • @Conditional注解加在配置类上时,需要使用ConfigurationCondition接口进行实现,通过ConfigurationPhase来指定条件匹配对配置类本身注册的影响。因为Condition接口的条件是在spring扫描候选组件的过程中执行的,所以在根据Bean进行条件判断时,需要注意此问题。如果是自定义的业务需求判断,不会受此影响。

    总结

    我们平常在使用spring或者spring MVC时,@Conditional 注解的使用可能并不是很多,但是在当下Spring Boot大行其道,并且Spring Boot对@Conditional进行了很多的扩展,所以了解@Conditional的使用及原理,也是对Spring Boot的基础学习做更多的铺垫。
    本文对@Conditional的使用进行了介绍,没有深入说明Condition的原理,这些内容将在后续的spring组件扫描过程中进行说明。

    学习永远都不是一件简单的事情,可以有迷茫,可以懒惰,但是前进的脚步永远都不能停止。

    不积跬步,无以至千里;不积小流,无以成江海;

    黑色的眼睛 の 个人博客

  • 相关阅读:
    Elasticsearch 配置优化
    一个Elasticsearch嵌套nested查询的实例
    apache kafka配置中request.required.acks含义
    filebeat配置介绍
    Linux nohup命令详解
    elasticsearch中如何手动控制全文检索结果的精准度
    elasticsearch中filter执行原理深度剖析(bitset机制与caching机制)
    logback与log4j比较
    Markdown常用语法
    Asp.NetCore3.1中JWT认证入门使用(一)
  • 原文地址:https://www.cnblogs.com/tangfd/p/11162614.html
Copyright © 2020-2023  润新知