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并使用即可。

如下示例代码:

java 复制代码
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到容器中,方便其它开发人员使用,否则就不装配。

示例代码如下:

java 复制代码
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,表示允许初始化,即允许装配到容器中
java 复制代码
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:将方法返回值注入容器
java 复制代码
@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做集成的需求举例说明。

示例代码如下:

java 复制代码
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不会重复注入
相关推荐
Flittly1 天前
【SpringSecurity新手村系列】(3)自定义登录页与表单认证
java·笔记·安全·spring·springboot
Flittly3 天前
【SpringSecurity新手村系列】(2)整合 MyBatis 实现数据库认证
java·安全·spring·springboot·安全架构
极光代码工作室3 天前
基于SpringBoot的在线考试系统
java·springboot·web开发·后端开发
YDS8294 天前
大营销平台 —— 抽奖规则决策树
java·springboot·ddd
码农张35 天前
自定义跨字段校验必填注解
springboot
格鸰爱童话5 天前
向AI学习项目技能(七)
学习·springboot
代码漫谈5 天前
微服务 vs 单体架构:架构选型、实战拆解与决策指南
java·微服务·springboot·springcloud
文慧的科技江湖5 天前
光储充一体化系统落地 PRD 全功能清单 - 慧知开源充电桩平台
java·mysql·开源·springboot·慧知开源充电桩平台·充电重复订单解决方案源码
Flittly6 天前
【SpringAIAlibaba新手村系列】(16)调用百度 MCP 服务
java·笔记·spring·ai·springboot
MegaDataFlowers7 天前
yaml配置注入
springboot