写在前面

  • 动态加载配置文件就是在程序运行的过程中实时监控配置文件的状态,在发生变化时重新加载,而无需停止程序。
  • 这里主要是介绍如何在Java环境中实现动态加载配置文件。

一、引入依赖包

  • commons-io主要是用于实现文件更改的监控。
<!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
<dependency>
        <groupId>commons-io</groupId>
        <artifactId>commons-io</artifactId>
        <version>2.11.0</version>
</dependency>

二、配置配置文件

  • 配置文件的格式这里使用JSON。

三、程序demo

  • JSON文件的解析用的是fastjson库;
  • load()函数用于加载配置文件;
  • 监听程序用了线程,这样可以避免阻塞主线程;
  • 记录到的配置项使用HashMap来保存,方便使用;
  • getConfig()函数来返回读到内存中的配置;
  • 注意load()getConfig()均对同一个变量进行操作,需要加锁。

程序如下:

import java.io.File;
import java.io.IOException;
import java.util.HashMap;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.monitor.FileAlterationListenerAdaptor;
import org.apache.commons.io.monitor.FileAlterationMonitor;
import org.apache.commons.io.monitor.FileAlterationObserver;

@Slf4j
public class DynamicConfig implements Runnable {

    // 指定文件名及相关路径
    private String filename = "config.properties";

    private String rootDir = System.getProperty("root.dir", System.getProperty("user.dir"));

    private String filePath = rootDir + File.separator + filename;

    // 用HashMap存放配置
    private HashMap<Integer, String> config = new HashMap<>();;

    public DynamicConfig() {
        // 使用默认配置文件路径
    }

    public DynamicConfig(String filePath) {
        // 使用传入的配置文件路径覆盖默认路径
        this.filePath = filePath;
    }

    // 返回config,已加锁
    public synchronized HashMap<Integer, String> getConfig() {
        return config;
    }

    // 加载配置文件更新config,已加锁
    private synchronized void load() {
        File file = new File(filePath);
        if (!file.exists()) {
            log.warn("File is not exist. file: {}", file.getAbsolutePath());
            return;
        }
        File configFile = new File(filePath);
        try {
            // 清空HashMap
            config.clear();

            // 加载配置文件
            String configContent = FileUtils.readFileToString(configFile, "UTF-8");
            JSONObject configJson = JSON.parseObject(configContent);

            log.info("Load config, file: {}", file.getAbsolutePath());
            // 打印当前加载的配置
            JSONArray  patterns = configJson.getJSONArray("patterns");
            for(int i=0;i<patterns.size();i++)
            {
                JSONObject pattern = patterns.getJSONObject(i);
                // 解析配置数组内容
                int pattern_id = pattern.getInteger("pattern_id");
                String method = pattern.getString("method");
                System.out.println(String.valueOf(pattern_id) + ":" + method);

                // 记录到HashMap
                config.put(pattern_id, method);
            }
        } catch (IOException e) {
            log.error("Load config error.", e);
        }
    }

    @Override
    public void run() {
        // 文件变动监听,监听的是这个目录
        FileAlterationObserver observer = new FileAlterationObserver(rootDir);
        observer.addListener(new FileAlterationListenerAdaptor() {
            @Override
            public void onFileCreate(File file) {
                // 当指定的文件创建的时候
                if (filename.equals(file.getName())) {
                    log.info("Config file create. file: {}", file.getAbsolutePath());
                    load();
                }
            }

            @Override
            public void onFileChange(File file) {
                // 当指定的文件修改的时候
                if (filename.equals(file.getName())) {
                    log.info("Config file change. file: {}", file.getAbsolutePath());
                    load();
                }
            }

            @Override
            public void onFileDelete(File file) {
                // 当指定的文件删除
                if (filename.equals(file.getName())) {
                    log.info("Config file delete file: {}", file.getAbsolutePath());
                }
            }
        });
        observer.checkAndNotify();
        FileAlterationMonitor monitor = new FileAlterationMonitor();
        monitor.addObserver(observer);
        try {
            monitor.start();
            log.info("Start file change monitor. Path: {}", rootDir);
        } catch (Exception e) {
            log.error("Init file change error.", e);
        }
    }
}

四、一些报错

1. SLF4J: No SLF4J providers were found.

  • 场景
  • 程序一开始运行时,在控制台输出报错。
  • 程序能够正常运行,但无法输出日志。
  • 原因
  • Slf4j可以看作是Log4j的标准接口,它本身不包含实现。
  • 因此Maven如果配置不当,很容易出现No SLF4J providers were found的错误,这表明程序无法生成可用的Slf4j实例。
  • 解决方法
  • Maven依赖需要配置两个包,如果是从https://mvnrepository.com/直接拷贝的话,需要把slf4j-simple<scope>test</scope>删掉,否则在编译的时候不起作用:
<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->
<dependency>
	<groupId>org.slf4j</groupId>
	<artifactId>slf4j-api</artifactId>
	<version>2.0.0-alpha5</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-simple -->
<dependency>
	<groupId>org.slf4j</groupId>
	<artifactId>slf4j-simple</artifactId>
	<version>2.0.0-alpha5</version>
</dependency>
  • 另外,实现@Slf4j注解可以避免每次写日志时都需要使用日志对象,直接用log即可。
  • @Slf4j注解需要Lombok插件,Maven配置如下:
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
	<groupId>org.projectlombok</groupId>
	<artifactId>lombok</artifactId>
	<version>1.18.24</version>
	<scope>provided</scope>
</dependency>