springboot1.x和2.x将配置属性绑定到对象上(代码片段)

wen-pan wen-pan     2023-03-26     153

关键词:

一、问题描述

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

②、待绑定的对象

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);
        );
    

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;


二、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
 */
public class MapPropertySource extends EnumerablePropertySource<Map<Object, Object>> 

    public MapPropertySource(String name, Map<Object, Object> source) 
        super(name, source);
    

    @Override
    public Object getProperty(String name) 
        return this.source.get(name);
    

    @Override
    public boolean containsProperty(String name) 
        return this.source.containsKey(name);
    

    @Override
    public String[] getPropertyNames() 
        Set<Object> set = this.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];
    


2、自定义PropertiesBinder

  • PropertiesBinder可以方便的将ConfigurableEnvironment里的属性按照指定前缀绑定到指定的对象上
  • 也可以将properties文件或yaml文件里的属性解析为Map集合后,将Map集合里的key-value按照指定的前缀绑定到指定的对象上
/**
 * 属性绑定器
 *
 * @author wenpanfeng 2022/11/23 10:57
 */
@Slf4j
public class PropertiesBinder 

    private PropertiesBinder() 
    

    /**
     * 绑定smart pool properties
     *
     * @param configurableEnvironment configurableEnvironment
     * @return SmartPoolProperties
     * @author wenpanfeng 2022/11/23 11:12
     */
    public static SmartPoolProperties bindSmartPoolProperties(ConfigurableEnvironment configurableEnvironment) 

        return bindProperties(configurableEnvironment, PrefixConst.ExecutorConfig.SMART_POOL_PREFIX, SmartPoolProperties.class);
    

    /**
     * 绑定properties to smartPoolProperties
     *
     * @param properties          properties
     * @param smartPoolProperties smartPoolProperties
     * @author wenpanfeng 2022/11/24 21:15
     */
    public static void bindSmartProperties(Map<Object, Object> properties, SmartPoolProperties smartPoolProperties) 

        bindProperties(properties, PrefixConst.ExecutorConfig.SMART_POOL_PREFIX, smartPoolProperties);
    

    /**
     * 绑定属性到target
     *
     * @param properties properties
     * @param prefix     绑定前缀
     * @param target     目标对象
     * @author wenpanfeng 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.bindSmartProperties", 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 PropertiesBindException(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 wenpanfeng 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 PropertiesBindException(String.format("Bind properties failed, prefix is [%s], Class is [%s]", prefix, clazz));
        
    

三、springboot2.x绑定方式

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

    private PropertiesBinder() 

    public static void bindDtpProperties(Map<?, Object> properties, DtpProperties dtpProperties) 
        ConfigurationPropertySource sources = new MapConfigurationPropertySource(properties);
        Binder binder = new Binder(sources);
        ResolvableType type = ResolvableType.forClass(DtpProperties.class);
        Bindable<?> target = Bindable.of(type).withExistingValue(dtpProperties);
        binder.bind(MAIN_PROPERTIES_PREFIX, target);
    

    public static void bindDtpProperties(Environment environment, DtpProperties dtpProperties) 
        Binder binder = Binder.get(environment);
        ResolvableType type = ResolvableType.forClass(DtpProperties.class);
        Bindable<?> target = Bindable.of(type).withExistingValue(dtpProperties);
        binder.bind(MAIN_PROPERTIES_PREFIX, target);
    

总结springboot1.x迁移到2.x需要注意的问题(代码片段)

  SpringBoot1.x和SpringBoot2.x版本差异化还是比较大的,有些三方依赖组件有些是基于2.0版本为标准升级的,当我们将项目由1.0升级到2.0时会出现依赖的方法不存在或方法错误,需要逐个去调整,下面总结了我们升级实践过程中遇... 查看详情

springboot1.x和2.x优雅重启实战

...待未完成的请求执行完成,这样可以保证数据的完整性。SpringBoot1.Ximportjava.util.concurrent.Executor;importjava.util.concurrent.ThreadPoo 查看详情

ideaspringboot1.xjunit单元测试

  目前最主流的单元测试框架是junit,其中springboot1.x系列主要使用junit4,springboot2.x主要使用junit5;mock类和打桩的主要框架是mockito,主要有1.x(springboot1.x依赖),2.x(springboot2.0,2.1依赖),3.x(springboot2.2依赖)三个版本。0、... 查看详情

springboot2.x下多数据源配置

...多数情况下可以参考这个教程进行配置。不过该教程适合springboot1.x版本,由于2.x版本修改默认连接池为Hikari,所以该教程中的配置需要进行一些修改才可适用于2.x。主要不同之处在于DataSource的初始化。所以可在每个数据源的conf... 查看详情

将标签绑定到对象属性

】将标签绑定到对象属性【英文标题】:Bindingalabeltoanobjectproperty【发布时间】:2013-04-0907:17:12【问题描述】:我正在介绍一个项目,其中我有一个类Einstein,它具有名称属性。name属性是私有的,但我有它的getter和setter。在mxml文... 查看详情

将 Grails 2.X 迁移到 3.X - 啥是配置文件?

】将Grails2.X迁移到3.X-啥是配置文件?【英文标题】:MigratingGrails2.Xto3.X-WhatareProfiles?将Grails2.X迁移到3.X-什么是配置文件?【发布时间】:2020-11-2417:12:34【问题描述】:我是一个相当新手的程序员,我一直在学习Grails2和3。为了我... 查看详情

[(ngModel)] 和 [ngModel] 将状态绑定到属性的区别?

】[(ngModel)]和[ngModel]将状态绑定到属性的区别?【英文标题】:Differencebetween[(ngModel)]and[ngModel]forbindingstatetoproperty?【发布时间】:2017-07-1904:44:50【问题描述】:这是一个模板示例:<inputtype="number"class="form-control"[(ngModel)]="overRideR... 查看详情

如何使用 Nginx 反向代理将 Express-Gateway “主机”配置属性绑定到 localhost?

】如何使用Nginx反向代理将Express-Gateway“主机”配置属性绑定到localhost?【英文标题】:HowtobindExpress-Gateway"host"configurationpropertytolocalhostwithNginxreverseproxy?【发布时间】:2020-11-2701:26:08【问题描述】:Express-Gateway无法绑定到... 查看详情

XAML,将 Width 和 Height 属性绑定到其他控件的相同属性

】XAML,将Width和Height属性绑定到其他控件的相同属性【英文标题】:XAML,bindingWidthandHeightpropertiestothesamepropertiesofothercontrol【发布时间】:2011-01-1007:58:39【问题描述】:我正在尝试创建反射效果,效果很好,只是我必须对一些值进... 查看详情

将 ViewModel 的属性绑定到 UserControl 的子控件和自身的 DependencyProperty

】将ViewModel的属性绑定到UserControl的子控件和自身的DependencyProperty【英文标题】:BindViewModel\'spropertytoUserControl\'schildcontrolanditselfDependencyProperty【发布时间】:2021-09-2205:37:20【问题描述】:我想要做的是将我的用户控件的标签绑定... 查看详情

springboot1.5.x升级到springboot2.2.13记录(代码片段)

目录为什么升级到2.xSpringBoot1.x和SpringBoot2.x的一些区别RelaxedPropertyResolver不可用groovy异常log4j-slf4j-implcannotbepresentwithlog4j-to-slf4overridingisdisabled其他相关版本升级参考为什么升级到2.x1.SpringBoot1.X官方已不再进行维护;2.SpringBoot1.X... 查看详情

避免将任何数字绑定到布尔属性

】避免将任何数字绑定到布尔属性【英文标题】:Avoidbindanynumbertoboolproperty【发布时间】:2020-04-1515:30:26【问题描述】:我有简单的带有模型的ASP.NETCoreWebApipublicclassModelpublicbool?Valueget;set;和端点[HttpPost]publicasyncTask<IActionResult>... 查看详情

自动将事件绑定到依赖属性值

】自动将事件绑定到依赖属性值【英文标题】:AutomaticallybindeventtoaDependencyPropertiesValue【发布时间】:2015-11-0912:57:24【问题描述】:我正在尝试设置从ToggleButton控件派生的自定义用户控件。我想设置两个新命令,CheckedCommand和Unche... 查看详情

通过转换器将多个属性绑定到一个值

】通过转换器将多个属性绑定到一个值【英文标题】:Bindingmultiplepropertiesthroughconvertertoonevalue【发布时间】:2016-06-2316:52:26【问题描述】:我正在使用MvvmCross4并尝试绑定到axml中ListView元素的backgroundColor。现在的问题是颜色取决... 查看详情

使用 Blazor 将输入文本动态绑定到类/对象属性

】使用Blazor将输入文本动态绑定到类/对象属性【英文标题】:Dynamicallybindinginput-texttoclass/objectpropertysusingBlazor【发布时间】:2020-06-0423:15:24【问题描述】:我正在尝试使用Blazor为类中的属性构建输入字段的动态列表,但无法弄清... 查看详情

springboot1.5.x升级到springboot2.2.13记录(代码片段)

目录为什么升级到2.xSpringBoot1.x和SpringBoot2.x的一些区别RelaxedPropertyResolver不可用groovy异常log4j-slf4j-implcannotbepresentwithlog4j-to-slf4overridingisdisabled其他相关版本升级参考为什么升级到2.x1.SpringBoot1.X官方已不再进行维护;2.Sp 查看详情

无法将文本绑定到 VueJS 模板中的 href 属性

】无法将文本绑定到VueJS模板中的href属性【英文标题】:UnabletobindtexttohrefattributeinVueJStemplate【发布时间】:2020-08-1002:11:12【问题描述】:我正在使用VueJS构建模板。根据我的“帖子”数据,我已经能够使用以下方法将文本绑定到... 查看详情

wpf学习第二十九章元素绑定——将元素绑定到一起(代码片段)

...必对源对象(在本例中是Slider控件)做任何改动。只需要配置源对象使其属性具有正确的值范围,通常进行如下配置:<SliderName="sliderFontSize"Margin="3"Minimum="1"Maximum="40"Value="10"TickFrequency="1"IsSnapToTickEnabled="True"Ti 查看详情