前言
项目启动时需要从一些配置文件中加载相关配置,在运行过程中,希望修改配置文件的内容,可以重新加载配置。
下面会提供一个基本示例,仅供参考。
示例
引入依赖
需要用到commons-io包:
<dependencies>
<!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
</dependency>
</dependencies>
java示例
package com.xuxd.demo.file.watch;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.monitor.FileAlterationListenerAdaptor;
import org.apache.commons.io.monitor.FileAlterationMonitor;
import org.apache.commons.io.monitor.FileAlterationObserver;
/**
* daily-demo.
*
* @author xuxd
* @date 2021-12-09 18:46:30
**/
@Slf4j
public class AppConfig extends HashMap<String, Object> {
// 指定文件名及相关路径
private String filename = "config.properties";
private String rootDir = System.getProperty("root.dir", System.getProperty("user.dir"));
private String filePath = rootDir + File.separator + filename;
{
// 构造对象实例的时候,开启监听并加载配置
watch();
}
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 (InputStream inputStream = new FileInputStream(configFile)) {
putAll(load(inputStream));
} catch (Exception e) {
log.error("Load config error.", e);
}
log.info("Load config, file: {}", file.getAbsolutePath());
// 打印当前加载的配置
this.forEach((k, v) -> {
System.out.println(k + "=" + v);
});
}
private Map<? extends String, ?> load(InputStream inputStream) throws IOException {
Properties properties = new Properties();
properties.load(inputStream);
return Collections.unmodifiableMap((Map) properties);
}
void watch() {
// 文件变动监听,监听的是这个目录
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);
}
}
}
说明
关键是示例中watch方法实现,主要就是监听指定目录,内部也是异步线程采用观察者模式实现的。
当该目录下文件变动,回调相应动作,因为这里只处理指定配置文件的变动,实现对配置文件的热加载。
注意,这只是个示例,实际项目上这样肯定不行的,图懒省事我是直接继承Map方法,但是容易触发并发问题(加载配置的时候同时在读取,另外也没有处理删除配置的情况),在实际场景中建议采用装饰器模式和copy on write思想来规避这些问题。
测试
测试代码
public class AppConfigMain {
public static void main(String[] args) throws InterruptedException {
new AppConfig();
Thread.currentThread().join();
}
}
运行过程中,修改配置: