在Java应用系统中,我们经常需要配置文件来定制系统行为,这些配置文件可能包括:类路径下的文件和文件夹、非类路径下的绝对路径和相对路径的文件和文件夹,在分布式环境中,还需要通过HTTP从统一集中的Web服务器中获得配置信息,如何对这些配置信息进行自动加载并实时检测变化呢?
Java分布式中文分词组件 - word分词已经实现了这个功能,我们看看是如何实现的:
package org.apdplat.word.util; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.MalformedURLException; import java.net.URL; import java.nio.file.FileVisitResult; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.SimpleFileVisitor; import java.nio.file.StandardWatchEventKinds; import java.nio.file.WatchEvent; import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPoolConfig; import redis.clients.jedis.JedisPubSub; /** * 资源变化自动检测 * @author 杨尚川 */ public class AutoDetector { private static final Logger LOGGER = LoggerFactory.getLogger(AutoDetector.class); //已经被监控的文件 private static final Set<String> fileWatchers = new HashSet<>(); private static final Set<String> httpWatchers = new HashSet<>(); private static final Map<DirectoryWatcher, String> resources = new HashMap<>(); private static final Map<DirectoryWatcher, ResourceLoader> resourceLoaders = new HashMap<>(); private static final Map<DirectoryWatcher.WatcherCallback, DirectoryWatcher> watcherCallbacks = new HashMap<>(); /** * 加载资源并自动检测资源变化 * 当资源发生变化的时候重新自动加载 * @param resourceLoader 资源加载逻辑 * @param resourcePaths 多个资源路径,用逗号分隔 */ public static void loadAndWatch(ResourceLoader resourceLoader, String resourcePaths) { LOGGER.info("开始加载资源"); LOGGER.info(resourcePaths); long start = System.currentTimeMillis(); List<String> result = new ArrayList<>(); for(String resource : resourcePaths.split("[,,]")){ try{ resource = resource.trim(); if(resource.startsWith("classpath:")){ //处理类路径资源 result.addAll(loadClasspathResource(resource.replace("classpath:", ""), resourceLoader, resourcePaths)); }else if(resource.startsWith("http:")){ //处理HTTP资源 result.addAll(loadHttpResource(resource, resourceLoader)); }else{ //处理非类路径资源 result.addAll(loadNoneClasspathResource(resource, resourceLoader, resourcePaths)); } }catch(Exception e){ LOGGER.error("加载资源失败:"+resource, e); } } LOGGER.info("加载资源 "+result.size()+" 行"); //调用自定义加载逻辑 resourceLoader.clear(); resourceLoader.load(result); long cost = System.currentTimeMillis() - start; LOGGER.info("完成加载资源,耗时"+cost+" 毫秒"); } /** * 加载类路径资源 * @param resource 资源名称 * @param resourceLoader 资源自定义加载逻辑 * @param resourcePaths 资源的所有路径,用于资源监控 * @return 资源内容 * @throws IOException */ private static List<String> loadClasspathResource(String resource, ResourceLoader resourceLoader, String resourcePaths) throws IOException{ List<String> result = new ArrayList<>(); LOGGER.info("类路径资源:"+resource); Enumeration<URL> ps = AutoDetector.class.getClassLoader().getResources(resource); while(ps.hasMoreElements()) { URL url=ps.nextElement(); LOGGER.info("类路径资源URL:"+url); if(url.getFile().contains(".jar!")){ //加载jar资源 result.addAll(load("classpath:"+resource)); continue; } File file=new File(url.getFile()); boolean dir = file.isDirectory(); if(dir){ //处理目录 result.addAll(loadAndWatchDir(file.toPath(), resourceLoader, resourcePaths)); }else{ //处理文件 result.addAll(load(file.getAbsolutePath())); //监控文件 watchFile(file, resourceLoader, resourcePaths); } } return result; } /** * 加载HTTP资源 * @param resource 资源URL * @param resourceLoader 资源自定义加载逻辑 * @return 资源内容 */ private static List<String> loadHttpResource(String resource, ResourceLoader resourceLoader) throws MalformedURLException, IOException { List<String> result = new ArrayList<>(); try (BufferedReader reader = new BufferedReader(new InputStreamReader(new URL(resource).openConnection().getInputStream(), "utf-8"))) { String line = null; while((line = reader.readLine()) != null){ line = line.trim(); if("".equals(line) || line.startsWith("#")){ continue; } result.add(line); } } watchHttp(resource, resourceLoader); return result; } private static void watchHttp(String resource, final ResourceLoader resourceLoader){ String[] attrs = resource.split("/"); final String channel = attrs[attrs.length-1]; if(httpWatchers.contains(channel)){ return; } httpWatchers.add(channel); Thread thread = new Thread(new Runnable() { @Override public void run() { String host = WordConfTools.get("redis.host", "localhost"); int port = WordConfTools.getInt("redis.port", 6379); String channel_add = channel+".add"; String channel_remove = channel+".remove"; LOGGER.info("redis服务器配置信息 host:" + host + ",port:" + port + ",channels:[" + channel_add + "," + channel_remove+"]"); while(true){ try{ JedisPool jedisPool = new JedisPool(new JedisPoolConfig(), host, port); final Jedis jedis = jedisPool.getResource(); LOGGER.info("redis守护线程启动"); jedis.subscribe(new HttpResourceChangeRedisListener(resourceLoader), new String[]{channel_add, channel_remove}); jedisPool.returnResource(jedis); LOGGER.info("redis守护线程结束"); break; }catch(Exception e){ LOGGER.info("redis未启动,暂停一分钟后重新连接"); try { Thread.sleep(60000); } catch (InterruptedException ex) { LOGGER.error(ex.getMessage(), ex); } } } } }); thread.setDaemon(true); thread.setName("redis守护线程,用于动态监控资源:"+channel); thread.start(); } private static final class HttpResourceChangeRedisListener extends JedisPubSub { private ResourceLoader resourceLoader; public HttpResourceChangeRedisListener(ResourceLoader resourceLoader){ this.resourceLoader = resourceLoader; } @Override public void onMessage(String channel, String message) { LOGGER.debug("onMessage channel:" + channel + " and message:" + message); if(channel.endsWith(".add")){ this.resourceLoader.add(message); }else if(channel.endsWith(".remove")){ this.resourceLoader.remove(message); } } @Override public void onPMessage(String pattern, String channel, String message) { LOGGER.debug("pattern:" + pattern + " and channel:" + channel + " and message:" + message); onMessage(channel, message); } @Override public void onPSubscribe(String pattern, int subscribedChannels) { LOGGER.debug("psubscribe pattern:" + pattern + " and subscribedChannels:" + subscribedChannels); } @Override public void onPUnsubscribe(String pattern, int subscribedChannels) { LOGGER.debug("punsubscribe pattern:" + pattern + " and subscribedChannels:" + subscribedChannels); } @Override public void onSubscribe(String channel, int subscribedChannels) { LOGGER.debug("subscribe channel:" + channel + " and subscribedChannels:" + subscribedChannels); } @Override public void onUnsubscribe(String channel, int subscribedChannels) { LOGGER.debug("unsubscribe channel:" + channel + " and subscribedChannels:" + subscribedChannels); } } /** * 加载非类路径资源 * @param resource 资源路径 * @param resourceLoader 资源自定义加载逻辑 * @param resourcePaths 资源的所有路径,用于资源监控 * @return 资源内容 * @throws IOException */ private static List<String> loadNoneClasspathResource(String resource, ResourceLoader resourceLoader, String resourcePaths) throws IOException { List<String> result = new ArrayList<>(); Path path = Paths.get(resource); boolean exist = Files.exists(path); if(!exist){ LOGGER.error("资源不存在:"+resource); return result; } boolean isDir = Files.isDirectory(path); if(isDir){ //处理目录 result.addAll(loadAndWatchDir(path, resourceLoader, resourcePaths)); }else{ //处理文件 result.addAll(load(resource)); //监控文件 watchFile(path.toFile(), resourceLoader, resourcePaths); } return result; } /** * 递归加载目录下面的所有资源 * 并监控目录变化 * @param path 目录路径 * @param resourceLoader 资源自定义加载逻辑 * @param resourcePaths 资源的所有路径,用于资源监控 * @return 目录所有资源内容 */ private static List<String> loadAndWatchDir(Path path, ResourceLoader resourceLoader, String resourcePaths) { final List<String> result = new ArrayList<>(); try { Files.walkFileTree(path, new SimpleFileVisitor<Path>() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { result.addAll(load(file.toAbsolutePath().toString())); return FileVisitResult.CONTINUE; } }); } catch (IOException ex) { LOGGER.error("加载资源失败:"+path, ex); } if(fileWatchers.contains(path.toString())){ //之前已经注册过监控服务,此次忽略 return result; } fileWatchers.add(path.toString()); DirectoryWatcher.WatcherCallback watcherCallback = new DirectoryWatcher.WatcherCallback(){ private long lastExecute = System.currentTimeMillis(); @Override public void execute(WatchEvent.Kind<?> kind, String path) { //一秒内发生的多个相同事件认定为一次,防止短时间内多次加载资源 if(System.currentTimeMillis() - lastExecute > 1000){ lastExecute = System.currentTimeMillis(); LOGGER.info("事件:"+kind.name()+" ,路径:"+path); synchronized(AutoDetector.class){ DirectoryWatcher dw = watcherCallbacks.get(this); String paths = resources.get(dw); ResourceLoader loader = resourceLoaders.get(dw); LOGGER.info("重新加载数据"); loadAndWatch(loader, paths); } } } }; DirectoryWatcher directoryWatcher = DirectoryWatcher.getDirectoryWatcher(watcherCallback, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_MODIFY, StandardWatchEventKinds.ENTRY_DELETE); directoryWatcher.watchDirectoryTree(path); watcherCallbacks.put(watcherCallback, directoryWatcher); resources.put(directoryWatcher, resourcePaths); resourceLoaders.put(directoryWatcher, resourceLoader); return result; } /** * 加载文件资源 * @param path 文件路径 * @return 文件内容 */ private static List<String> load(String path) { List<String> result = new ArrayList<>(); try{ InputStream in = null; LOGGER.info("加载资源:"+path); if(path.startsWith("classpath:")){ in = AutoDetector.class.getClassLoader().getResourceAsStream(path.replace("classpath:", "")); }else{ in = new FileInputStream(path); } try(BufferedReader reader = new BufferedReader(new InputStreamReader(in,"utf-8"))){ String line; while((line = reader.readLine()) != null){ line = line.trim(); if("".equals(line) || line.startsWith("#")){ continue; } result.add(line); } } }catch(Exception e){ LOGGER.error("加载资源失败:"+path, e); } return result; } /** * 监控文件变化 * @param file 文件 */ private static void watchFile(final File file, ResourceLoader resourceLoader, String resourcePaths) { if(fileWatchers.contains(file.toString())){ //之前已经注册过监控服务,此次忽略 return; } fileWatchers.add(file.toString()); LOGGER.info("监控文件:"+file.toString()); DirectoryWatcher.WatcherCallback watcherCallback = new DirectoryWatcher.WatcherCallback(){ private long lastExecute = System.currentTimeMillis(); @Override public void execute(WatchEvent.Kind<?> kind, String path) { if(System.currentTimeMillis() - lastExecute > 1000){ lastExecute = System.currentTimeMillis(); if(!path.equals(file.toString())){ return; } LOGGER.info("事件:"+kind.name()+" ,路径:"+path); synchronized(AutoDetector.class){ DirectoryWatcher dw = watcherCallbacks.get(this); String paths = resources.get(dw); ResourceLoader loader = resourceLoaders.get(dw); LOGGER.info("重新加载数据"); loadAndWatch(loader, paths); } } } }; DirectoryWatcher fileWatcher = DirectoryWatcher.getDirectoryWatcher(watcherCallback, StandardWatchEventKinds.ENTRY_MODIFY, StandardWatchEventKinds.ENTRY_DELETE); fileWatcher.watchDirectory(file.getParent()); watcherCallbacks.put(watcherCallback, fileWatcher); resources.put(fileWatcher, resourcePaths); resourceLoaders.put(fileWatcher, resourceLoader); } public static void main(String[] args){ AutoDetector.loadAndWatch(new ResourceLoader(){ @Override public void clear() { System.out.println("清空资源"); } @Override public void load(List<String> lines) { for(String line : lines){ System.out.println(line); } } @Override public void add(String line) { System.out.println("add:"+line); } @Override public void remove(String line) { System.out.println("remove:"+line); } }, "d:/DIC, d:/DIC2, d:/dic.txt, classpath:dic2.txt,classpath:dic"); } }
相关推荐
1. **系统钩子(System Hooks)**:系统钩子是Windows操作系统提供的一种机制,允许应用程序监控系统级别的事件,如键盘输入、鼠标移动或文件系统变化。在Java中,我们通常需要通过JNI调用C/C++编写的动态链接库...
4. **缓存管理**:系统可能包含更新检测机制,当内容变化时自动重新生成静态页面。 5. **部署**:将生成的静态页面部署到Web服务器的静态资源目录。 总的来说,Java在处理静态文件方面提供了多种解决方案,从简单的...
3. **资源更新**:除了Java类,JRebel还能处理HTML、CSS、JavaScript等资源文件的修改,同样实现即时生效。 使用JRebel的步骤大致如下: 1. **下载和安装**:首先需要从官方网站下载JRebel的jar文件,或者通过IDE的...
在H5游戏中,资源版本号通常用于标识资源的更新状态,当有新的资源发布时,版本号会递增,客户端检测到版本号变化后,会强制更新资源,以保证用户体验。这种方式可以有效避免因为缓存问题导致的新功能无法正常显示或...
在IT行业中,Java是一种广泛应用的编程语言,尤其在企业级应用和系统开发中占据着重要地位。本主题聚焦于利用Java来实现USB设备的自动监控,以及当USB设备插入时,能够自动复制其中特定格式的文件到本地系统。这个...
Java 3DMenu 界面源码,有人说用到游戏中不错,其实平时我信编写Java应用程序时候也能用到吧,不一定非要局限于游戏吧,RES、SRC资源都有,都在压缩包内。 Java zip压缩包查看程序源码 1个目标文件 摘要:Java源码...
2. 监听:开启监听器,开始接收系统发送的文件变化通知。 3. 处理事件:接收到事件后,根据事件类型执行相应的处理逻辑,如记录日志、触发其他程序、更新用户界面等。 4. 错误处理:捕获并处理可能出现的异常,确保...
Java 3DMenu 界面源码,有人说用到游戏中不错,其实平时我信编写Java应用程序时候也能用到吧,不一定非要局限于游戏吧,RES、SRC资源都有,都在压缩包内。 Java zip压缩包查看程序源码 1个目标文件 摘要:Java...
JRebel 是一个流行的商业插件,它可以实时更新应用中的Java类、资源文件等,而无需重启服务器。在WebLogic环境中,安装并配置JRebel后,Java代码的改动会立即反映到运行中的应用中。但请注意,这需要购买许可证,...
Java 3DMenu 界面源码,有人说用到游戏中不错,其实平时我信编写Java应用程序时候也能用到吧,不一定非要局限于游戏吧,RES、SRC资源都有,都在压缩包内。 Java zip压缩包查看程序源码 1个目标文件 摘要:Java...
Java人脸识别系统是一种利用计算机视觉技术来识别人脸特征的软件应用程序,它基于深度学习和图像处理算法,能够从图像或视频流中检测、识别和追踪人脸。在Java平台上,开发者可以利用开源库和框架来构建这样的系统,...
Java 3DMenu 界面源码 5个目标文件 内容索引:Java源码,窗体界面,3DMenu Java 3DMenu 界面源码,有人说用到游戏中不错,其实平时我信编写Java应用程序时候也能用到吧,不一定非要局限于游戏吧,RES、SRC资源都有,都...
Java应用程序调优是优化Java应用程序性能的关键步骤,它涉及到对应用程序、JVM(Java虚拟机)以及操作系统层面的深入理解。在本篇讲解中,我们将重点关注HP提供的Java应用调优技术,包括HP JVM、性能工具及其对Java...
SkyWalking 是一个强大的开源项目,专门用于监视和分析Java应用程序的运行情况,提供全面的应用性能监控(APM)解决方案。这个系统能够帮助开发者和运维人员深入理解应用程序的性能瓶颈,及时发现并解决问题,确保...
这种方式能够实时捕捉到几乎所有的文件变化,提高了效率并降低了资源消耗。 为了实现这个功能,开发者通常需要编写程序来调用这些系统API,创建一个监听器并设置回调函数,以便在发生文件变化时执行特定的操作,如...
在Java项目中,"Mario"可能是一个包含所有源代码、资源文件和配置的文件夹,其中可能有如"Mario.java"的主程序文件,它是游戏的入口点,控制游戏的启动和运行。其他可能的文件和子目录可能包括: - "src":存放源...
Java 3DMenu 界面源码 5个目标文件 内容索引:Java源码,窗体界面,3DMenu Java 3DMenu 界面源码,有人说用到游戏中不错,其实平时我信编写Java应用程序时候也能用到吧,不一定非要局限于游戏吧,RES、SRC资源都有,都...
Java 3DMenu 界面源码,有人说用到游戏中不错,其实平时我信编写Java应用程序时候也能用到吧,不一定非要局限于游戏吧,RES、SRC资源都有,都在压缩包内。 Java zip压缩包查看程序源码 1个目标文件 摘要:Java...
- **第三方工具**:也有一些专门的工具,如File Watcher(Windows)、fsnotifier(macOS/Linux)等,可以帮助用户直观地监控文件变化。 4. 文件目录监视的关键特性: - **实时性**:能及时捕获到文件的任何变动。...
Java 3DMenu 界面源码 5个目标文件 内容索引:Java源码,窗体界面,3DMenu Java 3DMenu 界面源码,有人说用到游戏中不错,其实平时我信编写Java应用程序时候也能用到吧,不一定非要局限于游戏吧,RES、SRC资源都有,都...