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


    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    转换成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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    如果是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;
     	
     	...   
    }    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    如何动态获取非固定长度数组配置项?
    其次我们要观察这里的需求,因为要获取的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());
        }
    }
    
    
    • 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
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

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

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

    可以看到我们这里因为要循环读取配置项,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)));
                }
            }
        }
    }
    
    • 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
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    然后还需要结合上述说明的@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();
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

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

  • 相关阅读:
    Linux音频系统编程之芯片平台适配功放Codec Driver解读
    【题解】NowCoder DP4 最小花费爬楼梯
    Leetcode 198. House Robber
    一文带你了解【深度学习】中CNN、RNN、LSTM、DBN等神经网络(图文解释 包括各种激活函数)
    netsh int ipv4 show dynamicport tcp动态端口port设置
    php脚本执行timeout
    【云原生】FlexCloud 云数据转HTTP开发接口操作
    【C++笔试强训】第二十二天
    使用helm快速安装 grafana&prometheus
    零基础自学SQL课程 | CASE函数
  • 原文地址:https://blog.csdn.net/qq_24950043/article/details/133011128