From c9e575ff2ffdd9de537fe0938684221070a1a576 Mon Sep 17 00:00:00 2001 From: litongjava Date: Sun, 3 Sep 2023 16:37:25 -1000 Subject: [PATCH 1/2] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E8=8B=B1=E6=96=87=20READ?= =?UTF-8?q?ME?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 + README_en.md | 198 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 200 insertions(+) create mode 100644 README_en.md diff --git a/README.md b/README.md index f7198eb..1fd39c2 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # jfinal-undertow +[中文](README.md) [English](README_en.md) + #### 项目介绍 jfinal-undertow 用于开发、部署由 jfinal 开发的 web 项目。独创 HotSwapClassLoader + HotSwapWatcher 以 321 行代码极简实现热加载开发与部署,前无古人,后必有模仿者 diff --git a/README_en.md b/README_en.md new file mode 100644 index 0000000..fdd9edc --- /dev/null +++ b/README_en.md @@ -0,0 +1,198 @@ +## jfinal-undertow + +[中文](README.md) [English](README_en.md) + +#### Project Introduction +jfinal-undertow is used for the development and deployment of web projects developed by jfinal. It innovatively introduces the HotSwapClassLoader + HotSwapWatcher, achieving hot-reloading for both development and deployment with just 321 lines of code. This approach is unprecedented, and will surely inspire others to follow suit. + +## I. Quick Start +### 1: Add maven dependency +``` + + com.jfinal + jfinal-undertow + 3.5 + +``` + +Note: Remove the previous maven dependency on jetty-server. + +### 2: Create a main method and launch the project in eclipse or IDEA. +``` +UndertowServer.start(AppConfig.class); +``` + +Here, AppConfig is a subclass inherited from JFinalConfig. The previous JFinal.start(...) usage is no longer needed. + +## II. Quick Packaging and Deployment +### 1: Change the packaging type in the header of pom.xml from war to jar. +``` +jar +``` + +### 2: Add maven-jar-plugin to pom.xml. +``` + + + org.apache.maven.plugins + maven-jar-plugin + 2.6 + + + + *.* + + + +``` +This plugin is solely to prevent configuration files from being included in the jar. If packaging as a fatjar, this plugin is unnecessary. + +### 3: Add maven-assembly-plugin to pom.xml. +``` + + org.apache.maven.plugins + maven-assembly-plugin + 3.1.0 + + + make-assembly + package + + single + + + + + ${project.artifactId} + + false + + true + + + package.xml + + + ${project.build.directory}/ + + + + +``` + +### 4: Add a packaging descriptor file, package.xml, to the project root. Contents are as follows: +``` + + + + release + + + + dir + zip + + + + + true + + + + + ${basedir}/src/main/resources + config + + + + + ${basedir}/src/main/webapp + webapp + + + WEB-INF + WEB-INF/web.xml + + + + + + ${basedir} + + + 755 + unix + + *.sh + + + + ${basedir} + + 755 + windows + + *.bat + + + + + + + + + + + + lib + + + +``` + +### 5: Add project run script files to the project root directory. +This project provides jfinal.sh and jfinal.bat scripts in the root directory. jfinal.sh is for Linux/Mac systems, while jfinal.bat is for Windows. Ensure to modify the MAIN_CLASS variable in these scripts to point to your project entry, e.g.: +``` +MAIN_CLASS=com.yourpackage.YourMainClass +``` +These script files are optional. You can write your own startup scripts based on personal preference. + +### 6: Run the packaging command in the terminal. +``` +mvn clean package +``` + +### 7: Deployment +Navigate to the project's target/your-project-release directory and run ./jfinal.sh start to launch the project. The target directory will also contain a your-project-release.zip file. This is a compressed zip file of the directory generated in the fifth step. Upload and extract this file on the server to deploy. You can modify the package.xml to change the generated filename or opt not to generate this file. + +## III. Advantages of jfinal-undertow: +1: Ultra-fast startup, 5 to 8 times faster than Tomcat. The jfinal.com official website starts within 1.5 seconds. +2: A brilliantly simple hot-deployment design ensures lightning-fast and lightweight hot-reloading, enhancing the development experience. +3: Performance surpasses Tomcat and Jetty, suitable as a replacement for production environments. +4: Undertow is designed for embedding, allowing direct deployment in production environments without downloading or configuring services, making it ideal for microservices development and deployment. +5: Say goodbye to web.xml, Tomcat, and Jetty, saving a significant amount of packaging and deployment time. It makes development, packaging, and deployment a joy. +6: Feature-rich, supporting classHotSwap, WebSocket, gzip compression, servlets, filters, sessionHotSwap, and more. +7: Supports both fatjar and non-fatjar packaging modes, preparing jfinal for upcoming microservices functionality. +8: Unified development, packaging, and deployment processes. The entire workflow requires no adjustments or modifications to any part of the project, truly achieving rapid development to rapid deployment. +9: The above are just some of the features of jfinal-undertow. For more useful functionalities like the fatjar packaging mode, please refer to the official jfinal documentation. \ No newline at end of file -- Gitee From 36e8d1edc3f397c17bda138e3b60313e63b2e93b Mon Sep 17 00:00:00 2001 From: litongjava Date: Sun, 3 Sep 2023 16:40:30 -1000 Subject: [PATCH 2/2] =?UTF-8?q?=E4=BD=BF=E7=94=A8executorService.scheduleA?= =?UTF-8?q?tFixedRate=20=E6=9B=BF=E4=BB=A3=20=E6=AD=BB=E5=BE=AA=E7=8E=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../undertow/hotswap/HotSwapWatcher.java | 309 +++++++++--------- 1 file changed, 161 insertions(+), 148 deletions(-) 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 587fcab..5043b7d 100644 --- a/src/main/java/com/jfinal/server/undertow/hotswap/HotSwapWatcher.java +++ b/src/main/java/com/jfinal/server/undertow/hotswap/HotSwapWatcher.java @@ -1,12 +1,12 @@ /** * Copyright (c) 2011-2021, James Zhan 詹波 (jfinal@126.com). - * + *

* Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

* Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -30,6 +30,10 @@ import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + import com.jfinal.server.undertow.UndertowKit; import com.jfinal.server.undertow.UndertowServer; @@ -37,150 +41,159 @@ import com.jfinal.server.undertow.UndertowServer; * 监听 class path 下 .class 文件变动,触发 UndertowServer.restart() */ public class HotSwapWatcher extends Thread { - - protected UndertowServer undertowServer; - - // protected int watchingInterval = 1000; // 1900 与 2000 相对灵敏 - protected int watchingInterval = 500; - - protected List watchingPaths; - private WatchKey watchKey; - protected volatile boolean running = true; - - public HotSwapWatcher(UndertowServer undertowServer) { - setName("HotSwapWatcher"); - // 避免在调用 deploymentManager.stop()、undertow.stop() 后退出 JVM - setDaemon(false); - setPriority(Thread.MAX_PRIORITY); - - this.undertowServer = undertowServer; - this.watchingPaths = buildWatchingPaths(); - } - - protected List buildWatchingPaths() { - Set watchingDirSet = new HashSet<>(); - String[] classPathArray = System.getProperty("java.class.path").split(File.pathSeparator); - for (String classPath : classPathArray) { - buildDirs(new File(classPath.trim()), watchingDirSet); - } - - List dirList = new ArrayList(watchingDirSet); - Collections.sort(dirList); - - List pathList = new ArrayList(dirList.size()); - for (String dir : dirList) { - pathList.add(Paths.get(dir)); - } - - return pathList; - } - - private void buildDirs(File file, Set watchingDirSet) { - if (file.isDirectory()) { - watchingDirSet.add(file.getPath()); - - File[] fileList = file.listFiles(); - for (File f : fileList) { - buildDirs(f, watchingDirSet); - } - } - } - - public void run() { - try { - doRun(); - } catch (Throwable e) { - throw new RuntimeException(e); - } - } - - protected void doRun() throws IOException { - WatchService watcher = FileSystems.getDefault().newWatchService(); - addShutdownHook(watcher); - - for (Path path : watchingPaths) { - path.register( - watcher, - // StandardWatchEventKinds.ENTRY_DELETE, - StandardWatchEventKinds.ENTRY_MODIFY, - StandardWatchEventKinds.ENTRY_CREATE - ); - } - - while (running) { - try { - // watchKey = watcher.poll(watchingInterval, TimeUnit.MILLISECONDS); // watcher.take(); 阻塞等待 - // 比较两种方式的灵敏性,或许 take() 方法更好,起码资源占用少,测试 windows 机器上的响应 - watchKey = watcher.take(); - - if (watchKey == null) { - // System.out.println(System.currentTimeMillis() / 1000); - continue ; - } - } catch (Throwable e) { // 控制台 ctrl + c 退出 JVM 时也将抛出异常 - running = false; - if (e instanceof InterruptedException) { // 另一线程调用 hotSwapWatcher.interrupt() 抛此异常 - // while 循环没有通过 while (running && !Thread.currentThread().isInterrupted()) 控制流程,下一行代码可以不需要 - Thread.currentThread().interrupt(); // Restore the interrupted status - } - 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 ; - } - } - } - - resetWatchKey(); - } - } - - private void resetWatchKey() { - if (watchKey != null) { - watchKey.reset(); - watchKey = null; - } - } - - /** - * 添加关闭钩子在 JVM 退出时关闭 WatchService - * - * 注意:addShutdownHook 方式添加的回调在 kill -9 pid 强制退出 JVM 时不会被调用 - * kill 不带参数 -9 时才回调 - */ - protected void addShutdownHook(WatchService watcher) { - Runtime.getRuntime().addShutdownHook(new Thread(() -> { - try { - watcher.close(); - } catch (Throwable e) { - UndertowKit.doNothing(e); - } - })); - } - - public void exit() { - running = false; - try { - this.interrupt(); - } catch (Throwable e) { - UndertowKit.doNothing(e); - } - } - + + protected UndertowServer undertowServer; + + // protected int watchingInterval = 1000; // 1900 与 2000 相对灵敏 + protected int watchingInterval = 500; + + protected List watchingPaths; + private WatchKey watchKey; + protected volatile boolean running = true; + + public HotSwapWatcher(UndertowServer undertowServer) { + setName("HotSwapWatcher"); + // 避免在调用 deploymentManager.stop()、undertow.stop() 后退出 JVM + setDaemon(false); + setPriority(Thread.MAX_PRIORITY); + + this.undertowServer = undertowServer; + this.watchingPaths = buildWatchingPaths(); + } + + protected List buildWatchingPaths() { + Set watchingDirSet = new HashSet<>(); + String[] classPathArray = System.getProperty("java.class.path").split(File.pathSeparator); + for (String classPath : classPathArray) { + buildDirs(new File(classPath.trim()), watchingDirSet); + } + + List dirList = new ArrayList(watchingDirSet); + Collections.sort(dirList); + + List pathList = new ArrayList(dirList.size()); + for (String dir : dirList) { + pathList.add(Paths.get(dir)); + } + + return pathList; + } + + private void buildDirs(File file, Set watchingDirSet) { + if (file.isDirectory()) { + watchingDirSet.add(file.getPath()); + + File[] fileList = file.listFiles(); + for (File f : fileList) { + buildDirs(f, watchingDirSet); + } + } + } + + public void run() { + try { + doRun(); + } catch (Throwable e) { + throw new RuntimeException(e); + } + } + + protected void doRun() throws IOException { + ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor(); + WatchService watcher = FileSystems.getDefault().newWatchService(); + addShutdownHook(watcher); + + for (Path path : watchingPaths) { + path.register( + watcher, + // StandardWatchEventKinds.ENTRY_DELETE, + StandardWatchEventKinds.ENTRY_MODIFY, + StandardWatchEventKinds.ENTRY_CREATE + ); + } + +// while (running) { +// watch(watcher); +// } + executorService.scheduleAtFixedRate(() -> { + watch(watcher); + }, 0, 500, TimeUnit.MILLISECONDS); + } + + private void watch(WatchService watcher) { + try { + // watchKey = watcher.poll(watchingInterval, TimeUnit.MILLISECONDS); // watcher.take(); 阻塞等待 + // 比较两种方式的灵敏性,或许 take() 方法更好,起码资源占用少,测试 windows 机器上的响应 + watchKey = watcher.take(); + + if (watchKey == null) { + // System.out.println(System.currentTimeMillis() / 1000); + //continue ; + return; + } + } catch (Throwable e) { // 控制台 ctrl + c 退出 JVM 时也将抛出异常 + running = false; + if (e instanceof InterruptedException) { // 另一线程调用 hotSwapWatcher.interrupt() 抛此异常 + // while 循环没有通过 while (running && !Thread.currentThread().isInterrupted()) 控制流程,下一行代码可以不需要 + Thread.currentThread().interrupt(); // Restore the interrupted status + } + return; + } + + 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; + } + } + } + + resetWatchKey(); + } + + private void resetWatchKey() { + if (watchKey != null) { + watchKey.reset(); + watchKey = null; + } + } + + /** + * 添加关闭钩子在 JVM 退出时关闭 WatchService + * + * 注意:addShutdownHook 方式添加的回调在 kill -9 pid 强制退出 JVM 时不会被调用 + * kill 不带参数 -9 时才回调 + */ + protected void addShutdownHook(WatchService watcher) { + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + try { + watcher.close(); + } catch (Throwable e) { + UndertowKit.doNothing(e); + } + })); + } + + public void exit() { + running = false; + try { + this.interrupt(); + } catch (Throwable e) { + UndertowKit.doNothing(e); + } + } + // public static void main(String[] args) throws InterruptedException { // HotSwapWatcher watcher = new HotSwapWatcher(null); // watcher.start(); -- Gitee