package org.xforth.config.client; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.annotation.PreDestroy; import java.io.FileReader; import java.io.IOException; import java.net.URISyntaxException; import java.nio.file.*; import java.util.Enumeration; import java.util.Map; import java.util.Properties; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * 单例 * 本地配置管理,同时监控配置配置文件变化,动态变更ConfigBundle */ public class LocalConfig implements IDynamicConfig,IPropertiesExport{ private static final Logger logger = LoggerFactory.getLogger(LocalConfig.class); private static WatchService watcher = null; private static final ExecutorService executorService = Executors.newFixedThreadPool(1); private static volatile ConcurrentHashMap<String,String> localConfigMap = new ConcurrentHashMap<String, String>(); static { try { watcher = FileSystems.getDefault().newWatchService(); } catch (IOException e) { logger.error("init local config watch fail:"+e); } } private String configFile; public LocalConfig(String configFile,boolean isDynamic){ this.configFile = configFile; try { loadConfig(configFile); if(isDynamic) registerWatcher(); } catch (Exception e) { throw new RuntimeException(e); } } @Override public void registerWatcher() { try { Path dir = Paths.get(this.getClass().getClassLoader().getResource(configFile).toURI()).getParent(); logger.info("start watch dir:{}",dir); WatchKey watchKey = dir.register(watcher, StandardWatchEventKinds.ENTRY_CREATE,StandardWatchEventKinds.ENTRY_MODIFY, StandardWatchEventKinds.ENTRY_DELETE); } catch (Exception e) { logger.error("registerWatcher error:{}",e); return; } Runnable checkConfigLoop = new Runnable() { @Override public void run() { while(true){ WatchKey key = null; try { key = watcher.take(); } catch (InterruptedException e) { logger.error("checkConfigLoop InterruptedException:{}",e); } for (WatchEvent<?> event : key.pollEvents()) { WatchEvent.Kind kind = event.kind(); if (kind == StandardWatchEventKinds.OVERFLOW) { continue; } // 目录监视事件的上下文是文件名 WatchEvent<Path> evt = (WatchEvent<Path>)event; Path name = evt.context(); if(name.toString().equals(configFile)){ logger.info("local config file:{} change ,load new config...",configFile); try { loadConfig(configFile); }catch(Throwable e){ logger.error("checkConfigLoop loadConfig error:{}",e); } }else{ logger.debug("ignore event:it was not a config file changing as name:{}",name); } } boolean valid = key.reset(); if (!valid) { break; // Exit if directory is deleted } } } }; executorService.submit(checkConfigLoop); } @Override public void loadConfig(Object configFile){ try { synchronized (this) { ConcurrentHashMap<String,String> newConfigMap = new ConcurrentHashMap<>(); Path file = Paths.get(this.getClass().getClassLoader().getResource(configFile.toString()).toURI()); FileReader fr = new FileReader(file.toFile().getPath()); Properties properties = new Properties(); properties.load(fr);//load()方法可通过字符流直接加载文件 Enumeration enumeration = properties.propertyNames(); while (enumeration.hasMoreElements()) { String propertyName = enumeration.nextElement().toString(); logger.debug("localConfigMap put key:{},value:{}",propertyName, properties.getProperty(propertyName)); newConfigMap.put(propertyName, properties.getProperty(propertyName)); } if(newConfigMap.size()>0) localConfigMap = newConfigMap; } } catch (Exception e) { logger.error("loadLocalConfig error:{}",e); } } @Override public String get(String key) { return localConfigMap.get(key); } @Override public Properties loadAll() { Properties prop = new Properties(); for (Map.Entry<String, String> entry : localConfigMap.entrySet()){ prop.put(entry.getKey(),entry.getValue()); } return prop; } @PreDestroy public void destroy(){ if(!(executorService.isShutdown()||executorService.isTerminated())) { executorService.shutdownNow(); } } }