Java案例如何实现配置热加载?

wen java案例 2

本文目录导读:

Java案例如何实现配置热加载?

  1. 使用Spring Cloud Config(推荐)
  2. 使用Apache Commons Configuration
  3. 手动实现文件监听器
  4. 使用分布式配置中心
  5. 使用Spring Event + 定时刷新
  6. 完整示例:文件系统监听器
  7. 最佳实践建议

在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);
    }
}

最佳实践建议

  1. 选择合适方案

    • 简单应用:手动文件监听器
    • Spring项目:Spring Cloud Config + @RefreshScope
    • 微服务架构:Apollo/Nacos等配置中心
  2. 注意事项

    • 确保线程安全
    • 添加异常处理
    • 考虑配置验证机制
    • 做好版本控制
    • 添加日志记录
  3. 性能优化

    • 使用原子操作更新配置
    • 避免频繁读取配置
    • 合理设置检查间隔
  4. 容错处理

    • 配置加载失败使用旧配置
    • 配置变化通知事务处理
    • 提供回退机制

选择哪种方案取决于你的具体场景、项目规模和团队技术栈,对于大多数Spring项目,使用Spring Cloud Config或配置中心是推荐方案。

抱歉,评论功能暂时关闭!