diff --git a/src/main/java/com/jfinal/server/undertow/hotswap/HotSwapWatcher.java b/src/main/java/com/jfinal/server/undertow/hotswap/HotSwapWatcher.java index 248c3ca32191df119f07ce2337f0193e00f7fd14..551d75eecdd539372f530763518db6cf57eccd1b 100644 --- a/src/main/java/com/jfinal/server/undertow/hotswap/HotSwapWatcher.java +++ b/src/main/java/com/jfinal/server/undertow/hotswap/HotSwapWatcher.java @@ -30,6 +30,8 @@ import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.concurrent.TimeUnit; + import com.jfinal.server.undertow.UndertowKit; import com.jfinal.server.undertow.UndertowServer; @@ -42,7 +44,8 @@ public class HotSwapWatcher extends Thread { // protected int watchingInterval = 1000; // 1900 与 2000 相对灵敏 protected int watchingInterval = 500; - + protected int guessInterval = 3; //部分IDE会在class文件修改事件发生后1-2ms内删除class文件 + protected List watchingPaths; private WatchKey watchKey; protected volatile boolean running = true; @@ -93,7 +96,8 @@ public class HotSwapWatcher extends Thread { throw new RuntimeException(e); } } - + + protected void doRun() throws IOException { WatchService watcher = FileSystems.getDefault().newWatchService(); addShutdownHook(watcher); @@ -101,7 +105,7 @@ public class HotSwapWatcher extends Thread { for (Path path : watchingPaths) { path.register( watcher, - // StandardWatchEventKinds.ENTRY_DELETE, + StandardWatchEventKinds.ENTRY_DELETE, StandardWatchEventKinds.ENTRY_MODIFY, StandardWatchEventKinds.ENTRY_CREATE ); @@ -124,29 +128,73 @@ public class HotSwapWatcher extends Thread { } break ; } - + List> watchEvents = watchKey.pollEvents(); - for(WatchEvent event : watchEvents) { - String fileName = event.context().toString(); - if (fileName.endsWith(".class")) { - if (undertowServer.isStarted()) { - undertowServer.restart(); - resetWatchKey(); - - while((watchKey = watcher.poll()) != null) { - // System.out.println("---> poll() "); - watchKey.pollEvents(); - resetWatchKey(); - } - - break ; + Set changeFiles = new HashSet<>(); + analyzeChangeFiles(changeFiles, watchEvents); + + + if(!changeFiles.isEmpty()){ + try { + resetWatchKey(); + //部分IDE会在class文件修改后1-2ms删除文件,并创建新class文件 + if((watchKey = watcher.poll(guessInterval, TimeUnit.MILLISECONDS)) != null){ + List> guessEvents = watchKey.pollEvents(); + analyzeChangeFiles(changeFiles, guessEvents); } + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + if ( !changeFiles.isEmpty() && undertowServer.isStarted()) { + undertowServer.restart(); + resetWatchKey(); + + while((watchKey = watcher.poll()) != null) { + // System.out.println("---> poll() "); + watchKey.pollEvents(); + resetWatchKey(); } + } resetWatchKey(); } } + + /** + * 此方法用于辅助判断真实修改行为 + * 并非所有IDE 的自动编译功能都是直接对class文件进行修改操作,例如 IntelliJ IDEA 就会针对变化的class文件先进行删除操作, + * 然后再创建新的class文件,然后对新创建的class文件进行修改操作,因此会产生一系列系统文件变化事件,如下: + * 1、class文件修改事件 + * 2、class文件删除事件 + * 3、class文件所在目录修改事件 + * 4、class文件创建事件 + * 5、class文件修改事件(真正写入class文件内容) + * 6、class文件所在目录修改事件 + * + * 为了兼容这类IDE,并不能在监听到class文件修改事件后便立即进行热加载过程,因为有些修改事件实际上是无效事件,比如上述事件1,虽然 + * 是class文件修改事件,但是立马又产生了class文件删除事件将class文件删除了,此时触发热加载,将会引起class文件不存在异常。 + * 因此当捕获到class文件修改事件后,不立即进行热加载,而是继续观察数毫秒,判断是否还有后续删除事件,如果有则视为此次修改事件等同于删除事件不 + * 进行热加载;如果没有才认为是真实修改行为,此时才进行热加载操作。 + * @param changeFiles 记录一次事件中发生变化的文件 + * @param watchEvents 监听到的文件变化事件List + * @return void + */ + private void analyzeChangeFiles(Set changeFiles, List> watchEvents){ + for(WatchEvent event : watchEvents) { + String fileName = event.context().toString(); + WatchEvent.Kind kind = event.kind(); + if (fileName.endsWith(".class")) { + if(kind == StandardWatchEventKinds.ENTRY_DELETE){ + changeFiles.remove(fileName); + }else{ + changeFiles.add(fileName); + } + } + } + } private void resetWatchKey() { if (watchKey != null) {