• springboot1.x和2.x将配置属性绑定到对象上


    一、问题描述

    1、描述

    • 在基于springboot进行封装自定义框架或对某个开源框架进行二次改造时我们经常会涉及到将application.yml或者application.properties中配置的属性绑定到某个类对应的属性上
    • 使用@Value@ConfigurationProperties这种方式就不多说了,使用比较简单,但是局限性也比较大,比如只能在容器启动过程中的特定阶段进行绑定,如果容器启动好了或者容器正常运行中,再想去将动态读取到的配置属性绑定到某个对象上,那么@Value@ConfigurationProperties是做不到的
      • 比如:监听到配置中心配置发生变更,此时我们需要将变更的配置绑定到某个对象上(或者替换environment对象里的某个属性值)
      • 比如:在容器启动过程中(配置属性还没有绑定到@Value@ConfigurationProperties标识的类上),此时我们需要将properties配置文件里的某些属性读取出来(映射到某个对象上),
      • 比如:【多数据源组件开发】在spring启动过程中利用【BeanDefinitionRegistry + 动态读取配置文件配置的多个数据源配置】来动态的向容器里注册自定义数据源的bean定义信息,以便spring启动时能将这些自定义的数据源注入容器
    • 所以我们需要一个灵活的配置属性 <—> Java对象的映射工具类,在springboot2.x中提供了非常方便的org.springframework.boot.context.properties.bind.Binder来进行绑定,但是springboot1.x中并没有这么方便的Binder,所以需要我们自己改造一下

    2、示例

    比如:在容器已经启动完成并且运行过程中将如下配置绑定到SmartPoolProperties对象上

    ①、待绑定的属性
    # 动态线程池之重试线程池配置
    smart.pool.config.executors.retryExecutor.corePoolSize=1
    smart.pool.config.executors.retryExecutor.maximumPoolSize=5
    smart.pool.config.executors.retryExecutor.queueCapacity=256
    smart.pool.config.executors.retryExecutor.keepAliveTime=30
    smart.pool.config.executors.retryExecutor.threadNamePrefix=retry-executor
    smart.pool.config.executors.retryExecutor.awaitTerminationSeconds=30
    smart.pool.config.executors.retryExecutor.rejectedExecutionHandler=AbortPolicy
    # 动态线程池之订单线程池配置
    smart.pool.config.executors.orderExecutor.corePoolSize=1
    smart.pool.config.executors.orderExecutor.maximumPoolSize=5
    smart.pool.config.executors.orderExecutor.queueCapacity=256
    smart.pool.config.executors.orderExecutor.keepAliveTime=30
    smart.pool.config.executors.orderExecutor.threadNamePrefix=order-executor
    smart.pool.config.executors.orderExecutor.awaitTerminationSeconds=30
    smart.pool.config.executors.orderExecutor.rejectedExecutionHandler=AbortPolicy
    # 动态线程池之会员线程池配置
    smart.pool.config.executors.customerExecutor.corePoolSize=1
    smart.pool.config.executors.customerExecutor.maximumPoolSize=5
    smart.pool.config.executors.customerExecutor.queueCapacity=256
    smart.pool.config.executors.customerExecutor.keepAliveTime=30
    smart.pool.config.executors.customerExecutor.threadNamePrefix=customer-executor
    smart.pool.config.executors.customerExecutor.awaitTerminationSeconds=30
    smart.pool.config.executors.customerExecutor.rejectedExecutionHandler=AbortPolicy
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    ②、待绑定的对象
    public class SmartPoolProperties implements InitializingBean {
    
        /**
         * 线程池配置集合
         */
        private Map<String, ThreadPoolProperties> executors;
    
        /**
         * 配置文件类型(用于刷新线程池配置)
         *
         * @see ConfigFileTypeEnum
         */
        private String configFileType = ConfigFileTypeEnum.PROPERTIES.getValue();
    
        @Override
        public void afterPropertiesSet() throws Exception {
            if (Objects.isNull(executors)) {
                return;
            }
            executors.forEach((threadPoolName, properties) -> {
                String poolName = properties.getThreadPoolName();
                if (StringUtils.isNotBlank(poolName) && !poolName.equals(threadPoolName)) {
                    throw new SmartPoolExecutorException(String.format("threadPoolName is different, " +
                            "the first is [%s] and the second is [%s]", threadPoolName, poolName));
                }
                properties.setThreadPoolName(threadPoolName);
            });
        }
    }
    
    • 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

    ThreadpoolProperties

    public class ThreadPoolProperties {
        /**
         * 核心线程数,默认5
         */
        protected int corePoolSize = 5;
    
        /**
         * 最大线程数,默认20
         */
        protected int maximumPoolSize = 20;
    
        /**
         * 队列容量,默认1024 (一旦确定,禁止更新)
         */
        protected int queueCapacity = 1024;
    
        /**
         * 保活秒数,默认300s
         */
        protected long keepAliveTime = 300;
    
        /**
         * Timeout unit.
         */
        private TimeUnit unit = TimeUnit.SECONDS;
    
        /**
         * 线程池拒绝策略名称,默认 {@link RejectedTypeEnum#CALLER_RUNS_POLICY}
         */
        protected String rejectedExecutionHandler = RejectedTypeEnum.CALLER_RUNS_POLICY.getName();
    
        /**
         * 允许核心线程超时,默认false
         */
        protected boolean allowCoreThreadTimeOut = false;
    }
    
    
    • 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

    二、springboot1.x绑定方式

    • 具体原理可以参照springboot里如何将environment对象里属性绑定到指定对象上即可,可以看看org.springframework.boot.bind.PropertySourcesBinder#bindTo

    1、定义一个MapPropertySource

    • 如果你的map中key类型是string的话,那么可以不用自定义下面这个MapPropertySource,可以直接使用springboot提供的org.springframework.core.env.MapPropertySource即可
    • 我这里是由于业务需要,Map里的key必须是Object类型,所以无法直接使用org.springframework.core.env.MapPropertySource,因此自己拓展了一下 org.springframework.core.env.MapPropertySource
    /**
         * {@link org.springframework.core.env.PropertySource} that reads keys and values from a {@code Map} object.
         *
         * @author wenpanfeng
         * @see org.springframework.core.env.PropertiesPropertySource
         */
        static class MapPropertySource extends EnumerablePropertySource<Map<Object, Object>> {
    
            public MapPropertySource(String name, Map<Object, Object> source) {
                super(name, source);
            }
    
            @Override
            public Object getProperty(@NonNull String name) {
                return source.get(name);
            }
    
            @Override
            public boolean containsProperty(String name) {
                return source.containsKey(name);
            }
    
            @NonNull
            @Override
            public String[] getPropertyNames() {
                Set<Object> set = source.keySet();
                if (CollectionUtils.isNotEmpty(set)) {
                    String[] result = new String[set.size()];
                    Object[] objects = set.toArray();
                    for (int i = 0; i < objects.length; i++) {
                        result[i] = objects[i].toString();
                    }
                    return result;
                }
                return new String[0];
            }
    
        }
    
    • 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

    2、自定义PropertiesBinder

    • PropertiesBinder可以方便的将ConfigurableEnvironment里的属性按照指定前缀绑定到指定的对象上
    • 也可以将properties文件或yaml文件里的属性解析为Map集合后,将Map集合里的key-value按照指定的前缀绑定到指定的对象上
    /**
     * springboot1.x属性绑定器
     *
     * @author wenpan 2022/11/23 10:57
     */
    @Slf4j
    public class PropertiesBinder {
    
        private PropertiesBinder() {
        }
    
        /**
         * 绑定属性到target
         *
         * @param properties properties
         * @param prefix     绑定前缀
         * @param target     目标对象
         * @author wenpan 2022/11/24 17:05
         */
        public static <T> void bindProperties(Map<Object, Object> properties, String prefix, T target) {
            try {
                log.info("------------>>>>>>>>>> start bind properties, prefix is {}, target is {}", prefix, target);
                MapPropertySource mapPropertySource = new MapPropertySource("PropertiesBinder.bindProperties", properties);
                MutablePropertySources propertySources = new MutablePropertySources();
                propertySources.addLast(mapPropertySource);
                PropertySourcesBinder binder = new PropertySourcesBinder(propertySources);
                binder.bindTo(prefix, target);
                log.info("------------>>>>>>>>>> end bind properties, prefix is {}, target is {}", prefix, target);
            } catch (Exception ex) {
                log.info("------------>>>>>>>>>> error bind properties, prefix is {}, target is {}", prefix, target);
                throw new RuntimeException(String.format("Bind properties failed, prefix is [%s], target is [%s]", prefix, target));
            }
        }
    
        /**
         * 属性绑定
         *
         * @param configurableEnvironment Environment
         * @param prefix                  属性前缀
         * @param clazz                   target clazz
         * @return T
         * @author wenpan 2022/11/23 11:04
         */
        public static <T> T bindProperties(ConfigurableEnvironment configurableEnvironment, String prefix, Class<T> clazz) {
            try {
                log.info("------------>>>>>>>>>> start bind properties, prefix is {}, clazz is {}", prefix, clazz);
                PropertySourcesBinder propertySourcesBinder = new PropertySourcesBinder(configurableEnvironment);
                T instance = clazz.newInstance();
                propertySourcesBinder.bindTo(prefix, instance);
                log.info("------------>>>>>>>>>> end bind properties, prefix is {}, clazz is {}", prefix, clazz);
                return instance;
            } catch (Exception ex) {
                log.info("------------>>>>>>>>>> error bind properties, prefix is {}, clazz is {}", prefix, clazz);
                throw new RuntimeException(String.format("Bind properties failed, prefix is [%s], Class is [%s]", prefix, clazz));
            }
        }
    
    • 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
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56

    三、springboot2.x绑定方式

    • springboot2.x就非常方便了,官方提供了Binder,直接使用即可,不过多介绍!!!
    /**
     * 

    * springboot2.x提供的属性绑定器 *

    * * @author wenpan 2022/12/10 1:34 下午 */
    public class PropertiesBinder2X { private PropertiesBinder2X() { } /** *

    * 从 Map类型的properties 中按照prefix查找属性值并绑定到targetObj对象的对应属性上 * properties 可以是自己从配置文件中读取的key-value键值对 *

    * * @param properties map * @param prefix 前缀 * @param targetObj 目标对象 * @author wenpan 2022/12/10 1:33 下午 */
    public static <T> void bindDtpProperties(Map<?, Object> properties, String prefix, T targetObj) { ConfigurationPropertySource sources = new MapConfigurationPropertySource(properties); Binder binder = new Binder(sources); ResolvableType type = ResolvableType.forClass(targetObj.getClass()); Bindable<?> target = Bindable.of(type).withExistingValue(targetObj); binder.bind(prefix, target); } /** *

    * 从environment中按照prefix查找属性值并绑定到targetObj对象的对应属性上 *

    * * @param environment environment * @param prefix 前缀 * @param targetObj 目标对象 * @author wenpan 2022/12/10 1:32 下午 */
    public static <T> void bindDtpProperties(Environment environment, String prefix, T targetObj) { Binder binder = Binder.get(environment); ResolvableType type = ResolvableType.forClass(targetObj.getClass()); Bindable<?> target = Bindable.of(type).withExistingValue(targetObj); binder.bind(prefix, target); } }
    • 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
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
  • 相关阅读:
    探索数字化节能降碳 广域铭岛助力电解铝行业碳达峰
    一文掌握Spring MVC REST风格开发
    如何长期有效维护客户关系,你真的了解你的客户吗?
    谈谈为什么要分库分表?
    【UNIAPP】踩坑问题记录(持续更新)
    新手勇闯LVS
    会计要素包括哪些内容
    图文详解uni-app PC端宽屏适配方案
    Java动态代理Proxy类
    简要介绍,一文了解营商环境评估
  • 原文地址:https://blog.csdn.net/Hellowenpan/article/details/128166638