在现代微服务或分布式系统中,动态配置管理是一项非常重要的功能,它允许我们在不重启服务的情况下,动态地更新配置项(如数据库连接、API限流配置、日志级别等)。在Java中,可以通过多种方式实现动态配置管理,包括使用配置中心(如Apollo、Nacos、Consul等)或本地的动态配置刷新机制。
我们将实现一个 DynamicConfigUtils
工具类,用于支持动态配置的加载和更新。工具类提供以下功能:
- 动态加载配置:从文件或配置中心加载配置。
- 监听配置更新:当配置文件发生变化时自动更新应用中的配置项。
- 手动刷新配置:支持手动调用刷新配置项。
- 配置缓存:将已加载的配置缓存到内存中,避免频繁读取外部文件。
一、工具类结构设计
我们将 DynamicConfigUtils
设计为单例模式,以确保配置的全局统一性。工具类将包含以下核心方法:
loadConfig()
:加载初始配置。getConfig()
:获取配置项的值。setConfig()
:动态设置或更新配置项的值。refreshConfig()
:手动刷新或监听外部变动来刷新配置。
二、DynamicConfigUtils 实现
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.file.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
public class DynamicConfigUtils {
private static DynamicConfigUtils instance;
private final Map<String, String> configCache = new HashMap<>();
private final String configFilePath;
private WatchService watchService;
/**
* 构造方法,传入配置文件路径
*
* @param configFilePath 配置文件路径
*/
private DynamicConfigUtils(String configFilePath) {
this.configFilePath = configFilePath;
loadConfig();
startConfigFileWatcher();
}
/**
* 获取单例实例
*
* @param configFilePath 配置文件路径
* @return DynamicConfigUtils实例
*/
public static synchronized DynamicConfigUtils getInstance(String configFilePath) {
if (instance == null) {
instance = new DynamicConfigUtils(configFilePath);
}
return instance;
}
/**
* 加载配置文件,并将配置项缓存到内存中
*/
private void loadConfig() {
try (FileInputStream inputStream = new FileInputStream(configFilePath)) {
Properties properties = new Properties();
properties.load(inputStream);
for (String key : properties.stringPropertyNames()) {
configCache.put(key, properties.getProperty(key));
}
System.out.println("配置文件加载完成:" + configCache);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 获取指定配置项的值
*
* @param key 配置项的键
* @return 配置项的值
*/
public String getConfig(String key) {
return configCache.get(key);
}
/**
* 动态设置配置项的值
*
* @param key 配置项的键
* @param value 配置项的值
*/
public void setConfig(String key, String value) {
configCache.put(key, value);
System.out.println("动态设置配置项:" + key + " = " + value);
}
/**
* 手动刷新配置文件,重新加载配置项
*/
public void refreshConfig() {
System.out.println("手动刷新配置文件...");
loadConfig();
}
/**
* 开启配置文件监视器,自动监听配置文件变化并刷新配置
*/
private void startConfigFileWatcher() {
try {
watchService = FileSystems.getDefault().newWatchService();
Path path = Paths.get(configFilePath).getParent();
path.register(watchService, StandardWatchEventKinds.ENTRY_MODIFY);
Thread watchThread = new Thread(() -> {
while (true) {
try {
WatchKey key = watchService.take();
for (WatchEvent<?> event : key.pollEvents()) {
if (event.kind() == StandardWatchEventKinds.ENTRY_MODIFY) {
Path changed = (Path) event.context();
if (changed.endsWith(Paths.get(configFilePath).getFileName())) {
System.out.println("配置文件已修改,重新加载配置...");
loadConfig();
}
}
}
key.reset();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
});
watchThread.setDaemon(true);
watchThread.start();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 关闭文件监视器,释放资源
*/
public void close() {
try {
if (watchService != null) {
watchService.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 测试方法,模拟配置文件的加载和更新
*/
public static void main(String[] args) throws InterruptedException {
// 配置文件路径
String configFilePath = "config.properties";
// 获取配置工具类实例并加载配置
DynamicConfigUtils configUtils = DynamicConfigUtils.getInstance(configFilePath);
System.out.println("当前配置项:key1 = " + configUtils.getConfig("key1"));
// 动态设置一个配置项
configUtils.setConfig("key2", "new_value");
System.out.println("当前配置项:key2 = " + configUtils.getConfig("key2"));
// 模拟手动刷新配置文件
Thread.sleep(5000);
configUtils.refreshConfig();
}
}
三、功能详解
1. 单例模式
为了确保动态配置工具类在应用程序中的全局统一性,我们采用单例模式实现,通过 getInstance()
方法来获取唯一的 DynamicConfigUtils
实例。
public static synchronized DynamicConfigUtils getInstance(String configFilePath) {
if (instance == null) {
instance = new DynamicConfigUtils(configFilePath);
}
return instance;
}
2. 配置文件加载
loadConfig()
方法会读取配置文件,并将配置项缓存到 configCache
(一个 HashMap
对象)中,确保配置在内存中可以快速读取。
private void loadConfig() {
try (FileInputStream inputStream = new FileInputStream(configFilePath)) {
Properties properties = new Properties();
properties.load(inputStream);
for (String key : properties.stringPropertyNames()) {
configCache.put(key, properties.getProperty(key));
}
System.out.println("配置文件加载完成:" + configCache);
} catch (IOException e) {
e.printStackTrace();
}
}
3. 动态获取与设置配置项
getConfig()
方法用于从缓存中获取指定的配置项值,setConfig()
方法用于动态设置或更新配置项,并将其保存到内存中。
public String getConfig(String key) {
return configCache.get(key);
}
public void setConfig(String key, String value) {
configCache.put(key, value);
System.out.println("动态设置配置项:" + key + " = " + value);
}
4. 配置文件监控
通过 WatchService
机制,我们可以监控配置文件的变动。一旦文件发生修改(ENTRY_MODIFY
事件),工具类会自动重新加载配置。
private void startConfigFileWatcher() {
try {
watchService = FileSystems.getDefault().newWatchService();
Path path = Paths.get(configFilePath).getParent();
path.register(watchService, StandardWatchEventKinds.ENTRY_MODIFY);
Thread watchThread = new Thread(() -> {
while (true) {
try {
WatchKey key = watchService.take();
for (WatchEvent<?> event : key.pollEvents()) {
if (event.kind() == StandardWatchEventKinds.ENTRY_MODIFY) {
Path changed = (Path) event.context();
if (changed.endsWith(Paths.get(configFilePath).getFileName())) {
System.out.println("配置文件已修改,重新加载配置...");
loadConfig();
}
}
}
key.reset();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
});
watchThread.setDaemon(true);
watchThread.start();
} catch (IOException e) {
e.printStackTrace();
}
}
5. 手动刷新配置
除了监听文件变化,工具类还提供了手动刷新配置的功能,通过 refreshConfig()
方法可以手动触发配置的重新加载。
public void refreshConfig() {
System.out.println("手动刷新配置文件...");
loadConfig();
}
四、使用示例
- 准备一个
config.properties
配置文件,内容如下:
key1=value1
key2=value2
- 运行
main()
方法,加载配置文件并打印内容。随后可以手动更新配置文件,工具类会
自动检测到修改并重新加载。
public static void main(String[] args) throws InterruptedException {
String configFilePath = "config.properties";
DynamicConfigUtils configUtils = DynamicConfigUtils.getInstance(configFilePath);
System.out.println("当前配置项:key1 = " + configUtils.getConfig("key1"));
configUtils.setConfig("key2", "new_value");
System.out.println("当前配置项:key2 = " + configUtils.getConfig("key2"));
Thread.sleep(5000);
configUtils.refreshConfig();
}
五、功能扩展建议
- 外部配置中心:可以通过集成Nacos、Apollo等配置中心实现分布式系统的统一配置管理。
- 缓存持久化:当配置发生变化时,可以将更新的配置持久化到本地文件或数据库。
- 配置监听器:为每个配置项提供监听器,当配置项发生变化时,自动通知相应的服务或模块更新。
六、总结
DynamicConfigUtils是一个Java动态配置工具类,用于在运行时动态配置应用程序的参数和属性。
该工具类提供了一系列静态方法,用于读取和设置配置文件中的参数和属性。可以通过指定配置文件的路径,读取配置文件中的键值对,并将其作为Map返回。也可以根据指定的键获取配置文件中对应的值。
除了读取配置文件,DynamicConfigUtils还提供了设置配置文件的方法。可以指定配置文件的路径和一组键值对,将这些键值对写入到配置文件中。
该工具类还提供了一些实用的方法,例如判断配置文件是否存在,判断配置文件中是否存在某个键等。
DynamicConfigUtils可以方便地读取和设置配置文件中的参数和属性,使得应用程序在运行时可以灵活地进行配置和调整。