spring利用commons-configuration2库读取property配置文件

仲孙疏珂
2023-12-01
一、commons-configuration2简介和maven依赖

commons-configuration2是很好用的配置文件读取工具库,支持很多配置文件格式,实际开发中常见的配置文件也就是property类型(后缀:.properties)。commons-configuration2较commons-configuration有很大改进,更好的支持配置自动重载,可以在不关闭服务器/应用程序的情况下直接修改配置文件,commons-configuration2自动读取修改后的配置,从而达到在不关闭服务器/应用程序的条件下调整程序流程和走向的目的。
commons-configuration2需要如下maven依赖:

<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-configuration2 -->
<dependency>
	<groupId>org.apache.commons</groupId>
	<artifactId>commons-configuration2</artifactId>
	<version>2.4</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-beanutils/commons-beanutils -->
<dependency>
	<groupId>commons-beanutils</groupId>
	<artifactId>commons-beanutils</artifactId>
	<version>1.9.3</version>
</dependency>

二、读取property配置和自动重载配置

我将读取property配置的功能封装成了一个spring bean,代码如下。该代码可直接粘贴复制拿来用,(需要将如下代码放到spring bean扫描路径上)或者(注释掉@Component注解然后将如下代码手动配置成spring bean)。总之不管怎样,只要能将如下代码做成一个spring管理的bean即可。如下代码有详细注释,配置自动重载功能在代码中有体现,详见注释(代码中写的是5秒钟检测一次配置是否需要重载,可自己根据实际情况修改为其他值)。

package com.szq.learn.springcomponent;

import java.io.File;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import javax.annotation.PreDestroy;

import org.apache.commons.configuration2.ConfigurationUtils;
import org.apache.commons.configuration2.ImmutableConfiguration;
import org.apache.commons.configuration2.PropertiesConfiguration;
import org.apache.commons.configuration2.builder.ReloadingFileBasedConfigurationBuilder;
import org.apache.commons.configuration2.builder.fluent.Parameters;
import org.apache.commons.configuration2.builder.fluent.PropertiesBuilderParameters;
import org.apache.commons.configuration2.ex.ConfigurationException;
import org.apache.commons.configuration2.reloading.PeriodicReloadingTrigger;
import org.springframework.stereotype.Component;

@Component
public class GlobalPropertyConfig {
    /**
     * 用HashMap存储文件名和对应的builder和trigger
     */
    private Map<String, BuilderAndTrigger> btMap = new HashMap<>();

    /**
     *  系统关闭时关闭trigger
     */
    @PreDestroy
    private void destroyMethod() {
        Set<Entry<String, BuilderAndTrigger>> entries = btMap.entrySet();
        for (Entry<String, BuilderAndTrigger> entry : entries) {
            entry.getValue().getTrigger().shutdown();
        }
    }

    /**
     * 获取配置
     * @param fileName 配置文件名,配置文件放在WEB-INF\classes目录下
     * @return 
     * @throws ConfigurationException 
     */
    public ImmutableConfiguration getConfiguration(final String fileName) throws ConfigurationException {
        BuilderAndTrigger bt = btMap.get(fileName);
        PropertiesConfiguration configuration = null;
        if (bt == null) { //builder不存在,生成新builder
            ReloadingFileBasedConfigurationBuilder<PropertiesConfiguration> builder = createConfiguration(fileName);
            configuration = builder.getConfiguration(); //检测builder是否成功生成,未成功生成则这里会抛出异常
            PeriodicReloadingTrigger trigger = new PeriodicReloadingTrigger(builder.getReloadingController(), null, 5,
                    TimeUnit.SECONDS); //声明配置重载触发器,5秒钟检测一次重载
            trigger.start(); //启动触发器
            btMap.put(fileName, new BuilderAndTrigger(builder, trigger)); //将文件名和对应的builder和trigger保存到btMap
        } else {
            configuration = bt.getBuilder().getConfiguration(); //若builder存在,则直接取该builder
        }
        return ConfigurationUtils.unmodifiableConfiguration(configuration); //返回不可修改的configuration
    }

    /**
     * 移除不再使用的配置
     * @param fileName 
     */
    public void removeConfiguration(final String fileName) {
        BuilderAndTrigger bt = btMap.get(fileName);
        if (bt != null) { //有builder和trigger存在
            bt.getTrigger().shutdown(); //停止trigger
            btMap.remove(fileName); //移除builder和trigger
        }
    }

    /**
     * 根据配置文件名生成新配置
     * @param fileName
     * @return 
     */
    private ReloadingFileBasedConfigurationBuilder<PropertiesConfiguration> createConfiguration(final String fileName) {
        PropertiesBuilderParameters parameters = new Parameters().properties().setFile(new File(fileName));
        return new ReloadingFileBasedConfigurationBuilder<>(PropertiesConfiguration.class).configure(parameters);
    }

    /**
     * @author Administrator 内部类,表示builder和其对应的trigger
     * @comment 
     */
    private static class BuilderAndTrigger {
        private ReloadingFileBasedConfigurationBuilder<PropertiesConfiguration> builder;
        private PeriodicReloadingTrigger trigger;

        public BuilderAndTrigger(ReloadingFileBasedConfigurationBuilder<PropertiesConfiguration> builder,
                PeriodicReloadingTrigger trigger) {
            this.builder = builder;
            this.trigger = trigger;
        }

        public ReloadingFileBasedConfigurationBuilder<PropertiesConfiguration> getBuilder() {
            return builder;
        }

        public PeriodicReloadingTrigger getTrigger() {
            return trigger;
        }

        @Override
        public String toString() {
            return "BuilderAndTrigger [builder=" + builder + ", trigger=" + trigger + "]";
        }

    }
}


三、使用示例

使用示例如下,代码有详细注释,基本上只需用getConfiguration()方法获取配置对象,然后再获取值即可。

package com.szq.learn.controller;

import java.util.HashMap;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import com.szq.learn.springcomponent.GlobalPropertyConfig;

@RestController
@RequestMapping(value = "/api", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public class APIController {
    @Autowired
    private GlobalPropertyConfig propertyConfig; //注入GlobalPropertyConfig
    private int i = 0;

    @RequestMapping("/**")
    public Map<String, Object> t() throws Exception {
        // 声明两个配置文件名,这俩配置文件需放到类加载路径上,一般为"WEB-INF\classes"目录下
        String fileName = "learn-reloadable.properties";
        String fileName2 = "learn-reloadable2.properties";
        // 获取配置文件配置,因为getConfiguration()是有缓存的,所以这样写速度几乎不会受影响
        System.out.println("-- config 1" + propertyConfig.getConfiguration(fileName).getString("a"));
        System.out.println("-- config 2" + propertyConfig.getConfiguration(fileName2).getString("a"));
        // 测试移除不再使用的配置,实际开发中removeConfiguration()方法基本上用不到,这里只是测试用
        if (i++ % 3 == 0) {
            propertyConfig.removeConfiguration(fileName2);
        }
        return new HashMap<>();
    }
}


四、干货

有人可能会这样写这行代码

PropertiesBuilderParameters parameters = new Parameters().properties().setFile(new File(fileName))
                .setListDelimiterHandler(new DefaultListDelimiterHandler(',')); //设置数组分隔符

也就是在设置configuration参数是直接设置一个数组分隔符,然后在程序里这样使用

String[] a = config.getStringArray("a"); //直接从properties类型配置文件读取上面设置的分割符组成的数组或者列表之类的数据

配置文件中的a属性可能如下所示

a = 1,2,3

这样使用是有问题的,日志会报异常,如下

java.beans.IntrospectionException: bad write method arg count: public final void org.apache.commons.configuration2.AbstractConfiguration.setProperty(java.lang.String,java.lang.Object)

查看原因,网址如下:
https://issues.apache.org/jira/browse/BEANUTILS-477
大致原因可能就是commons-beanutils库的问题。
所以如果要从properties类型配置文件读取数组或者列表之类的数据,最好的办法如下

String a = config.getString("a");
String[] b = a.split(","); //看你配置文件用什么分隔符,这里就写什么分隔符
List<String> c = Arrays.asList(b); //或者更进一步,做成列表

也就是说在读取配置文件的时候按照字符串来读取,然后手动将字符串做成自己需要的数组或者列表。


原创不易,转帖请注明出处-----shizhongqi

 类似资料: