本文目录导读:

- 使用Spring Cloud Config(推荐)
- 使用Apache Commons Configuration
- 手动实现文件监听器
- 使用分布式配置中心
- 使用Spring Event + 定时刷新
- 完整示例:文件系统监听器
- 最佳实践建议
在Java中实现配置热加载,主要目的是在不重启应用的情况下动态更新配置,以下是几种常见的实现方案,从简单到复杂:
使用Spring Cloud Config(推荐)
依赖配置
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
配置类示例
@RefreshScope
@Component
@ConfigurationProperties(prefix = "app.config")
public class AppConfig {
private String name;
private int timeout;
private List<String> allowedUrls;
// getter/setter
}
@RestController
public class ConfigController {
@Autowired
private AppConfig appConfig;
@GetMapping("/config")
public AppConfig getConfig() {
return appConfig;
}
}
触发刷新
# 发送POST请求刷新配置 curl -X POST http://localhost:8080/actuator/refresh
使用Apache Commons Configuration
依赖
<dependency>
<groupId>commons-configuration</groupId>
<artifactId>commons-configuration</artifactId>
<version>1.10</version>
</dependency>
实现代码
import org.apache.commons.configuration.PropertiesConfiguration;
import org.apache.commons.configuration.reloading.FileChangedReloadingStrategy;
public class HotReloadConfig {
private static PropertiesConfiguration config;
public static void init() throws Exception {
config = new PropertiesConfiguration("app.properties");
// 设置热加载策略:5秒检查一次文件变化
FileChangedReloadingStrategy strategy = new FileChangedReloadingStrategy();
strategy.setRefreshDelay(5000);
config.setReloadingStrategy(strategy);
}
public static String getProperty(String key) {
return config.getString(key);
}
public static int getIntProperty(String key) {
return config.getInt(key);
}
}
手动实现文件监听器
使用Java NIO WatchService
import java.nio.file.*;
import java.util.concurrent.Executors;
public class ConfigFileWatcher {
private static final String CONFIG_FILE = "app.properties";
private static Properties configProps = new Properties();
public static void startWatching() throws Exception {
Path configPath = Paths.get(CONFIG_FILE);
WatchService watchService = FileSystems.getDefault().newWatchService();
configPath.getParent().register(watchService,
StandardWatchEventKinds.ENTRY_MODIFY);
Executors.newSingleThreadExecutor().submit(() -> {
while (true) {
WatchKey key = watchService.take();
for (WatchEvent<?> event : key.pollEvents()) {
if (event.context().toString().equals(CONFIG_FILE)) {
reloadConfig();
}
}
key.reset();
}
});
}
private static synchronized void reloadConfig() {
try (InputStream input = new FileInputStream(CONFIG_FILE)) {
configProps.load(input);
System.out.println("配置已热加载: " + new Date());
} catch (Exception e) {
e.printStackTrace();
}
}
public static String getConfig(String key) {
return configProps.getProperty(key);
}
}
使用分布式配置中心
Apollo配置中心示例
@Configuration
@EnableApolloConfig
public class AppConfig {
@Value("${app.timeout:1000}")
private int timeout;
// 监听配置变化
@ApolloConfigChangeListener
private void onChange(ConfigChangeEvent changeEvent) {
if (changeEvent.isChanged("app.timeout")) {
timeout = Integer.parseInt(changeEvent.getChange("app.timeout").getNewValue());
System.out.println("timeout已更新为: " + timeout);
}
}
}
使用Spring Event + 定时刷新
@Component
public class DynamicConfigService {
private Map<String, String> configMap = new HashMap<>();
@PostConstruct
public void init() {
loadConfig();
scheduleRefresh();
}
private void loadConfig() {
// 从数据库或文件加载配置
// ...
}
private void scheduleRefresh() {
Executors.newScheduledThreadPool(1).scheduleAtFixedRate(
this::loadConfig, 0, 10, TimeUnit.SECONDS);
}
@EventListener
public void handleRefreshEvent(RefreshEvent event) {
loadConfig();
}
}
完整示例:文件系统监听器
import java.io.*;
import java.nio.file.*;
import java.util.*;
import java.util.concurrent.*;
import java.util.function.Consumer;
public class HotConfigLoader {
private final String configPath;
private final Map<String, String> config = new ConcurrentHashMap<>();
private final List<Consumer<Map<String, String>>> listeners = new CopyOnWriteArrayList<>();
public HotConfigLoader(String configPath) {
this.configPath = configPath;
loadConfig();
startWatch();
}
private void loadConfig() {
Properties props = new Properties();
try (InputStream input = new FileInputStream(configPath)) {
props.load(input);
config.clear();
props.forEach((k, v) -> config.put(k.toString(), v.toString()));
// 通知监听器
listeners.forEach(l -> l.accept(Collections.unmodifiableMap(config)));
} catch (IOException e) {
e.printStackTrace();
}
}
private void startWatch() {
Thread watcher = new Thread(() -> {
try {
Path path = Paths.get(configPath);
WatchService watchService = FileSystems.getDefault().newWatchService();
path.getParent().register(watchService, StandardWatchEventKinds.ENTRY_MODIFY);
while (true) {
WatchKey key = watchService.take();
for (WatchEvent<?> event : key.pollEvents()) {
if (event.context().toString().equals(path.getFileName().toString())) {
TimeUnit.SECONDS.sleep(1); // 等待文件写入完成
loadConfig();
System.out.println("配置文件已更新: " + new Date());
}
}
key.reset();
}
} catch (Exception e) {
e.printStackTrace();
}
});
watcher.setDaemon(true);
watcher.start();
}
public String get(String key) {
return config.get(key);
}
public void addListener(Consumer<Map<String, String>> listener) {
listeners.add(listener);
}
}
使用示例
public class Application {
public static void main(String[] args) {
HotConfigLoader config = new HotConfigLoader("app.properties");
// 添加监听器
config.addListener(newProps -> {
System.out.println("配置已更新: " + newProps.get("app.timeout"));
});
// 定时输出配置
Executors.newScheduledThreadPool(1).scheduleAtFixedRate(() -> {
System.out.println("当前timeout: " + config.get("app.timeout"));
}, 0, 2, TimeUnit.SECONDS);
}
}
最佳实践建议
-
选择合适方案:
- 简单应用:手动文件监听器
- Spring项目:Spring Cloud Config + @RefreshScope
- 微服务架构:Apollo/Nacos等配置中心
-
注意事项:
- 确保线程安全
- 添加异常处理
- 考虑配置验证机制
- 做好版本控制
- 添加日志记录
-
性能优化:
- 使用原子操作更新配置
- 避免频繁读取配置
- 合理设置检查间隔
-
容错处理:
- 配置加载失败使用旧配置
- 配置变化通知事务处理
- 提供回退机制
选择哪种方案取决于你的具体场景、项目规模和团队技术栈,对于大多数Spring项目,使用Spring Cloud Config或配置中心是推荐方案。