spring:实现初始化动态bean|获取对象型数组配置文件

2023-09-19 09:02:24

0. 引言

近期因为要完成实现中间件的工具包组件,其中涉及要读取对象型的数组配置文件,并且还要将其加载为bean,因为使用了spring 4.3.25.RELEASE版本,很多springboot的相关特性无法支持,因此特此记录,以方便后续同学有相同情况可以参考

1. 获取对象型数组配置文件

首先对象型数组配置文件如下所示:

minio.clients[0].name=xxx
minio.clients[0].endpoint=http://ip1:9000
minio.clients[0].access.key=admin
minio.clients[0].secret.key=asd
minio.clients[0].default.bucket=wu
minio.clients[1].name=yyy
minio.clients[1].endpoint=http://ip2:9000
minio.clients[1].access.key=admin
minio.clients[1].secret.key=asd
minio.clients[1].default.bucket=wu

转换成yml的格式如下:

minio:
  clients:
    - name: xxx
      endpoint: http://ip1:9000
      access:
        key: admin
      secret:
        key: asd
      default:
        bucket: wu
    - name: yyy
      endpoint: http://ip2:9000
        access:
          key: admin
        secret:
          key: asd
        default:
          bucket: wu

如果是springboot项目,我们直接用一个@ConfigurationProperties(prefix="minio.clients"),然后配置一个实体类就可以实现了,但这里因为遇到的是较老版本的spring项目,不支持该注解。于是尝试用其他方式实现。

@Value形式
首先来看@Value的确能够帮助我们读取到配置项,但是只能针对基础类型或者基础类型数组的配置项,对于我们对象项的数组配置文件,就不支持了,而spring中,除了这种方式,还有可以直接通过操作Environment对象来实现

Environment形式
可以看到通过environment.getProperty方法,是可以获取到我们想要的配置项的,于是这种方式明显是可行的。

同时Environment读取配置项时,要指定配置文件,于是需要借助@PropertySource来声明,同时因为这是一个工具包,也就是说配置文件可能会没有,没有则不用初始化bean,有对应配置文件再自动初始化bean,这是我们想要实现的

点开@PropertySource注解,我们可以看到一个ignoreResourceNotFound属性,从属性名已经告诉我们它的作用了,将其值设置为true,就可以实现配置文件存在时读取配置项,不存在时也不会报错

在这里插入图片描述

完整的示例如下:

@Configuration
@PropertySource(value = {"classpath:applicaiton.properties","classpath:minio.properties"}, ignoreResourceNotFound=true)
public class MinioMultiConfiguration {
 
    @Resource
    private Environment environment;
 	
 	...   
}    

如何动态获取非固定长度数组配置项?
其次我们要观察这里的需求,因为要获取的minio.clients配置实际上是多个,这里我们只是列举了两个,因为实现的是工具包,后续可能还会配置很多个,所以长度是非预期的。

那么我们就需要获取到这个数组的长度,如果是springboot,可以直接通过List<配置实体类> list定义的集合,获取集合长度即可,但是这里spring中,无法直接获取到数组长度,于是为了满足需求,只能采取了一个笨方法,直接定义一个长度配置minio.clients.length=2,后续大家这里有更好的办法可以留言讨论

然后我们就在代码中通过for循环获取配置项即可

2. 如何将bean注册到spring容器

同时因为我这里的需求还需要初始化对应的MinioClient,那就需要将创建的bean,注册到spring容器中,而注册到容器除了@Bean注解的方式,如下所示

@Configuration
public class MinioConfiguration {
    /**
     * 对象存储服务的url
     */
    @Value("${minio.endpoint:null}")
    private String endpoint;

    /**
     * 用户ID
     */
    @Value("${minio.access.key:null}")
    private String accessKey;

    /**
     * 账户密码
     */
    @Value("${minio.secret.key:null}")
    private String secretKey;

    /**
     * 默认桶名
     */
    @Value("${minio.default.bucket:null}")
    private String defaultBucketName = "wu";

    @Bean
    public MinioClient minioClient() throws Exception{
        MinioProperties minioProperties = new MinioProperties(endpoint, accessKey, secretKey, defaultBucketName);
        if(StringUtils.isEmpty(minioProperties.getEndpoint())){
            return null;
        }
        return new MinioClient(minioProperties.getEndpoint(), minioProperties.getAccessKey(), minioProperties.getSecretKey());
    }
}

还可以通过BeanFactory来进行注册,如下所示

DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
beanFactory.registerSingleton("beanName", new MinioClient());

可以看到我们这里因为要循环读取配置项,bean的个数是不定的,所以固定使用@Bean的形式肯定行不通,只能通过beanFactory进行注册

bean的注册时机
我们知道bean肯定是要在项目启动时就注册的,但是启动时也分了很多阶段,比如我们初始化好的bean实际上是要通过@Autowired@Resource引用的,所以我们肯定需要在这两个引用之前就注册好,否则就会报错bean找不到了。

spring中项目启动时执行方法,有几种方式,比较常用的有@PostConstruct注解的方式,但这种方式的执行顺序是@Bean > @Autowired > @PostConstruct,因此它肯定是不行了

于是我们尝试另一种方式通过申明BeanFactoryPostProcessor接口,实现postProcessBeanFactory方法,这可以对beanFactory进行一些自定义的修改,而我们就可以在这些修改中将bean注册进去

同时因为要通过Environment获取配置项,于是我们还需要申明下EnvironmentAware,通过setEnvironment方法把Environment注册进来,当然你也可以选择通过beanFactory.getBean("environment")获取

完整的示例代码如下:

public class MinioMultiBeanFactoryPostProcessor implements BeanFactoryPostProcessor, EnvironmentAware {
    private final static Logger log = LogManager.getLogger(MinioMultiBeanFactoryPostProcessor.class);

    private Environment environment;

    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        if(!environment.containsProperty("minio.clients.length")){
            log.error("未识别到minio.clients.length,取消配置多个minioClient");
            return;
        }
        Integer length = 0;
        try {
            length = environment.getProperty("minio.clients.length", Integer.class);
        }catch (Exception e){
            throw new RuntimeException("minioClient初始化失败,minio.clients.length数据类型为Int");
        }
        for (int i = 0; length != null && i < length; i++) {
            String name = environment.getProperty("minio.clients["+i+"].name");
            String endpoint = environment.getProperty("minio.clients["+i+"].endpoint");
            String access = environment.getProperty("minio.clients["+i+"].access.key");
            String secret = environment.getProperty("minio.clients["+i+"].secret.key");
            String bucket = environment.getProperty("minio.clients["+i+"].default.bucket");
            try{
                // 自定义对象
                MinioProperties minioProperties = new MinioProperties(endpoint, access, secret, bucket);
                // 创建client
                MinioClient minioClient = new MinioClient(minioProperties.getEndpoint(), minioProperties.getAccessKey(), minioProperties.getSecretKey());
                beanFactory.registerSingleton(name+"MinioClient", minioClient);
            }catch (Exception e){
                log.error(String.format("minioClient初始化失败:%s", ExceptionUtil.getErrorInfo(e)));
            }
        }
    }
}

然后还需要结合上述说明的@Bean > @Autowired的顺序,因为我们自定义的BeanFactoryPostProcessor现在还只是个单纯的类,我们也需要将其声明为bean才能实现修改BeanFactory的目的,于是通过@Bean来初始化

@Configuration
@PropertySource(value = {"classpath:application.properties","classpath:minio.properties"}, ignoreResourceNotFound=true)
public class MinioMultiConfiguration {

    @Bean
    public MinioMultiBeanFactoryPostProcessor minioMultiBeanFactoryPostProcessor(){
        return new MinioMultiBeanFactoryPostProcessor();
    }

}

至此,我们初始化动态bean的操作就完成了,以上方式适用于任何spring项目,对需要搭建中间包的项目更加适用,大家可以选择性参考

更多推荐

基于微信小程序的校园生活管理系统设计与实现(源码+lw+部署文档+讲解等)

文章目录前言运行环境学生微信端的主要功能有:管理员的主要功能有:具体实现截图视频演示为什么选择我自己的网站自己的小程序(小蔡coding)有保障的售后福利代码参考源码获取前言💗博主介绍:✌全网粉丝10W+,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ

机器学习:10种方法解决模型过拟合

机器学习:10种方法解决模型过拟合本文介绍机器学习/深度学习建模过程防止模型过拟合的10种有效方法:增加训练数据集交叉验证正则化合适的特征选择降低模型复杂度集成方法早停法EarlyStopping数据增强Dropout监控训练过程方法1:增加训练数据集增加更多的训练数据有助于防止过拟合,主要是因为更多的数据能够提供更全

【kafka实战】03 SpringBoot使用kafka生产者和消费者示例

本节主要介绍用SpringBoot进行开发时,使用kafka进行生产和消费一、引入依赖<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></de

【Java 基础篇】Java网络编程实战:P2P文件共享详解

Java网络编程是现代软件开发中不可或缺的一部分,因为它允许不同计算机之间的数据传输和通信。在本篇博客中,我们将深入探讨Java中的P2P文件共享,包括什么是P2P文件共享、如何实现它以及一些相关的重要概念。什么是P2P文件共享?P2P(Peer-to-Peer)文件共享是一种分布式计算模型,其中每个计算机或设备都可以

Java8实战-总结30

Java8实战-总结30并行数据处理与性能并行流正确使用并行流高效使用并行流小结并行数据处理与性能并行流正确使用并行流错用并行流而产生错误的首要原因,就是使用的算法改变了某些共享状态。下面是另一种实现对前n个自然数求和的方法,但这会改变一个共享累加器:publicstaticlongsideEffectSum(long

华为OD机试 - 特异性双端队列(Java 2023 B卷 100分)

目录专栏导读一、题目描述二、输入描述三、输出描述四、Java算法源码五、效果展示1、输入2、输出华为OD机试2023B卷题库疯狂收录中,刷题点这里专栏导读本专栏收录于《华为OD机试(JAVA)真题(A卷+B卷)》。刷的越多,抽中的概率越大,每一题都有详细的答题思路、详细的代码注释、样例测试,发现新题目,随时更新,全天C

单例模式有几种写法?

作者:猴子007选自:https://monkeysayhi.github.io“你知道茴香豆的‘茴’字有几种写法吗?”纠结单例模式有几种写法有用吗?有点用,面试中经常选择其中一种或几种写法作为话头,考查设计模式和codingstyle的同时,还很容易扩展到其他问题。这里讲解几种笔者常用的写法,但切忌生搬硬套,去记“茴

基于python的在线文档管理系统vue

本课题使用Python语言进行开发。代码层面的操作主要在PyCharm中进行,将系统所使用到的表以及数据存储到MySQL数据库中,方便对数据进行操作本课题基于WEB的开发平台1.运行环境:python3.7/python3.8。2.IDE环境:pycharm+mysql5.7;3.数据库工具:Navicat114.硬件

Xilinx ZYNQ 7000学习笔记五(Xilinx SDK 烧写镜像文件)

概述前面几篇讲了ZYNQ7000的启动过程,包括BootRom和FSBL的代码逻辑,其中关于FSBL代码对启动模式为JTAG被动启动没有进行分析,本篇将通过将JTAG的功能和通过XilinxSDK烧写镜像文件到flash来顺道把FSBL中的JTAG代码部分给讲解下。1.JTAGZYNQ7000系列SOC通过标准的JTA

【C++】string 之 assign、at、append函数的学习

前言在学习string类的过程中,我发现了assign这个函数,感觉很有用,就来记录一下assign函数原型:voidassign(size_typen,constT&x=T());voidassign(const_iteratorfirst,const_iteratorlast);assign函数有两种使用方式:第一

计算机视觉与深度学习-经典网络解析-AlexNet-[北邮鲁鹏]

这里写目录标题AlexNet参考文章AlexNet模型结构AlexNet共8层:AlexNet运作流程简单代码实现重要说明重要技巧主要贡献AlexNetAlexNet是一种卷积神经网络(ConvolutionalNeuralNetwork,CNN)的架构。它是由AlexKrizhevsky、IlyaSutskever和

热文推荐