• SpringBoot中Bean的条件装配


    概述

    众所周知,SpringBoot最腻害的地方就是容器,开发人员的日常工作就是编写bean,并由框架扫描存到容器里面,当程序跑起来的时候,各种bean协同工作完成了软件功能。

    那么容器是什么呢?

    从概念层面来讲,容器是一个池子;从物理层面来讲,容器是一个内存块。

    SpringBoot中默认是以单例形式装载bean的,所以大多数情况下,我们创建的bean对象在程序启动的时候都会被装载到org.springframework.beans.factory.support.DefaultSingletonBeanRegistry-singletonObjects 中,这是一个ConcurrentHashMap

    一方面我们需要关注容器中的bean能够提供哪些功能,这是程序工作的细粒度单元,是提供软件功能的基石;另外一方面我们也需要关注bean的装配,处理好它们的依赖关系才能让它们协同工作,共同完成造物主(码农)安排的任务。

    本文总结了在SpringBoot中常用的bean装配方法:

    • profile
    • conditional
    • ConditionalOn

    Profile

    profile 顾名思义,就是环境相关的装配条件。常见的如静态资源的存储,开发环境我们期望存储到硬盘,生产环境可能会存到MinIO中,那么此时可以通过profile根据环境的不同装配不同的文件存储处理bean到容器中,消费者无需关心当前什么环境,直接从容器中获取文件存储处理bean并使用即可。

    如下示例代码:

    
    import com.ramble.springbootzgnetsdk.service.DiskResourceServiceImpl;
    import com.ramble.springbootzgnetsdk.service.MinIoResourceServiceImpl;
    import com.ramble.springbootzgnetsdk.service.ResourceService;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.Profile;
    
    /**
     * Project     springboot-zgnetsdk
     * Package     com.ramble.springbootzgnetsdk.config
     * Class       ResourceServiceConfig
     * date        2024/1/26 10:49
     * author      cml
     * Email       liangchen_beijing@163.com
     * Description
     */
    
    
    @Configuration
    public class ResourceServiceConfig {
    
        @Profile("dev")
        @Bean
        public ResourceService initDiskResourceService() {
            return new DiskResourceServiceImpl();
        }
    
    
        @Profile("prod")
        @Bean
        public ResourceService initMinIoResourceService() {
            return new MinIoResourceServiceImpl();
        }
    }
    
    

    当前所属环境通过配置文件中的 spring.profiles.active 配置项约束

    • @Profile("dev"):当active的值为dev的时候,此注解注释的方法才会生效,结合@bean注解,方法的返回对象将被注入到容器中。
    • @Profile("!dev"):也可使用! 来表示取反的操作,即不是dev的环境此注解注释的方法才生效

    Conditional

    Conditional 位于org.springframework.context.annotation中,常常会结合Condition这个接口来完成条件装配,具体来说,Condition的match方法负责编写装配条件,返回true则表示允许装载,否则就不会装载。

    假设我们有这样一个需求,程序需要和海康网络设备SDK做集成,那么我们可以在配置文件中通过一个配置项来做开关,hikvision.enable,若此开关打开则装配海康网络设备SDK到容器中,方便其它开发人员使用,否则就不装配。

    示例代码如下:

    
    import org.springframework.context.annotation.Condition;
    import org.springframework.context.annotation.ConditionContext;
    import org.springframework.core.type.AnnotatedTypeMetadata;
    
    import java.util.Objects;
    
    /**
     * Project     springboot-hcnetsdk
     * Package     com.ramble.springboothcnetsdk.condition
     * Class       HikvisionSdkInitCondition
     *
     * @author 
     * Email
     * Description   海康sdk初始化条件装配
     * @date 2024/1/10 13:19
     */
    public class HikvisionSdkInitCondition implements Condition {
    
    
        /**
         * 装配规则,根据配置文件中hikvision.enable的值,为true或者1则装配
         *
         * @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 返回true,允许初始化;否则不允许
         */
        @Override
        public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
            String property = context.getEnvironment().getProperty("hikvision.enable");
            if (Objects.nonNull(property)) {
                return property.contains("true") || property.contains("1");
            } else {
                return false;
            }
        }
    }
    
    

    HikvisionSdkInitCondition ,首先定义一个条件类,此类继承Condition,通过重写matches方法来处理装配条件。

    • context:通过context对象的getEnvironment获取配置文件中的hikvision.enable
      配置项
    • 若hikvision.enable值为true或者1,表示允许初始化,即允许装配到容器中
    
    import com.ramble.springboothcnetsdk.condition.HikvisionSdkInitCondition;
    import com.ramble.springboothcnetsdk.support.HikvisionSupport;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Conditional;
    import org.springframework.context.annotation.Configuration;
    
    /**
     * Project     springboot-hcnetsdk
     * Package     com.ramble.springboothcnetsdk.config
     * Class       SdkInitConfig
     *
     * @author 
     * Email        liangchen_beijing@163.com
     * Description
     * @date 2024/1/10 13:27
     */
    
    @Configuration
    public class SdkInitConfig {
    
        /**
         * 初始化海康sdk。
         * 若满足Conditional注解,则向容器中注入 HikvisionSupport
         * @return
         */
        @Conditional(HikvisionSdkInitCondition.class)
        @Bean
        HikvisionSupport initHikvisionSdk() {
            return new HikvisionSupport();
        }
    }
    
    
    

    SdkInitConfig,定义一个sdk初始化配置类,通过此类将sdk装入容器中。

    • @Configuration:添加此注解,让容器可以扫描到此配置类
    • @Conditional(HikvisionSdkInitCondition.class):Conditional注解需要一个装配条件,当条件允许的时候就执行此方法,而条件具体逻辑已经在HikvisionSdkInitCondition中编写了
    • @Bean:将方法返回值注入容器
    
    @Autowired(required = false)
    private HikvisionSupport hikvisionSupport;
    
    

    在消费的地方注入的时候,必须添加 required = false,否则编译无法通过。

    ConditionalOn

    ConditionalOn是一个总称,其中包含了很多具体的注解,常用的如下:

    • @ConditionalOnBean:当容器中有指定Bean的条件下进行实例化。
    • @ConditionalOnMissingBean:当容器里没有指定Bean的条件下进行实例化。
    • @ConditionalOnClass:当classpath类路径下有指定类的条件下进行实例化。
    • @ConditionalOnMissingClass:当类路径下没有指定类的条件下进行实例化。
    • @ConditionalOnWebApplication:当项目是一个Web项目时进行实例化。
    • @ConditionalOnNotWebApplication:当项目不是一个Web项目时进行实例化。
    • @ConditionalOnProperty:当指定的属性有指定的值时进行实例化。
    • @ConditionalOnExpression:基于SpEL表达式的条件判断。
    • @ConditionalOnJava:当JVM版本为指定的版本范围时触发实例化。
    • @ConditionalOnResource:当类路径下有指定的资源时触发实例化。
    • @ConditionalOnJndi:在JNDI存在的条件下触发实例化。
    • @ConditionalOnSingleCandidate:当指定的Bean在容器中只有一个,或者有多个但是指定了首选的Bean时触发实例化。

    ConditionalOnProperty

    ConditionalOnProperty位于org.springframework.boot.autoconfigure.condition中,表示当配置文件中存在某配置项,并且该项值为具体某值的时候才装配bean。

    还是以程序需要和第三方网络设备SDK做集成的需求举例说明。

    示例代码如下:

    
    import com.ramble.springbootzgnetsdk.support.ZgSupport;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.Profile;
    
    /**
     * Project     springboot-zgnetsdk
     * Package     com.ramble.springbootzgnetsdk.config
     * Class       SdkInitConfig
     * date        2024/1/24 10:17
     * author      
     * Email       liangchen_beijing@163.com
     * Description
     */
    
    
    @Configuration
    @ConditionalOnProperty(value = "sdk.enable", havingValue = "true")
    public class SdkInitConfig {
    
    
        @Bean    
        @ConditionalOnMissingBean
        ZgSupport initZgSdk() {
            return new ZgSupport();
        }
    
    
    }
    
    
    • @Configuration:添加此注解,让容器可以扫描到此配置类
    • @ConditionalOnProperty(value = "sdk.enable", havingValue = "true"):当配置文件中存在sdk.enable配置项,并且配置项值为true的时候,才会执行此配置类
    • @Bean:将方法返回值注入容器
    • @ConditionalOnMissingBean:确保此bean不会重复注入
  • 相关阅读:
    揭秘BSN-DDC网络的自建城市算力中心
    【Linux】 ubuntu系统安装图形化界面
    风靡全球25年的重磅IP,新作沦为脚本乐园
    python+selenium的web自动化上传操作的实现
    Java 每个ClassLoad 加载的文件(夹)都有哪些
    指针-字符串替换
    【C++入门】(纯)虚函数和多态、抽象类、接口
    dataGrip导出导入的方式
    Windows下安装配置Nginx
    多主复制下处理写冲突(4)-多主复制拓扑
  • 原文地址:https://www.cnblogs.com/Naylor/p/17988988