功能目标:
支持热部署(Hot Deployment)功能,能够在不重启服务器的情况下加载新的 Web 应用。
监控应用目录的变化,当检测到新的 Web 应用时,自动加载该应用的 Servlet 和资源。
支持
web.xml
的重新加载和应用更新。
实现内容:
实现一个 目录监控机制,当检测到应用目录中的变化(如新增应用、修改或删除文件)时,自动加载或卸载应用。
支持 web.xml 文件的重新加载,使得配置更新后能自动生效。
提供一个简单的 文件监控线程,持续检测应用目录中的变化,并在变化发生时触发相关的加载或卸载操作。
15.1 热部署的设计
热部署的关键是动态地检测文件系统的变化,并自动加载或卸载 Web 应用。为此,我们可以借助 Java NIO 提供的 WatchService,实现对应用目录中文件的监控。
15.1.1 设计思路
文件监控:使用
WatchService
监控应用目录中的文件变化。包括新增、修改或删除操作。自动加载应用:当监控到新的应用目录或文件发生变化时,自动加载或更新应用的 Servlet 和配置。
重新加载配置:当
web.xml
文件发生变化时,重新加载该配置文件,并应用新的路由和初始化参数。
15.1.2 使用 WatchService
实现文件监控
package com.daicy.minitomcat;
import com.daicy.minitomcat.core.StandardContext;
import java.net.URL;
import java.nio.file.*;
import static com.daicy.minitomcat.HttpServer.CONF_PATH;
public class HotDeployment {
// private static final String APP_BASE_PATH = "/path/to/apps"; // Web 应用目录
public void startDeploymentMonitor() {
try {
URL url = getClass().getResource(CONF_PATH);
Path path = Paths.get(url.getPath());
WatchService watchService = FileSystems.getDefault().newWatchService();
// 注册监控事件:创建、修改和删除
path.register(watchService, StandardWatchEventKinds.ENTRY_CREATE,
StandardWatchEventKinds.ENTRY_MODIFY,
StandardWatchEventKinds.ENTRY_DELETE);
System.out.println("Monitoring directory for changes: " + path.toString());
// 监控目录变化
while (true) {
WatchKey key;
try {
key = watchService.take(); // 等待文件变化事件
} catch (InterruptedException e) {
System.out.println("Monitoring interrupted");
return;
}
// 处理监控到的事件
for (WatchEvent<?> event : key.pollEvents()) {
WatchEvent.Kind<?> kind = event.kind();
Path filePath = (Path) event.context();
System.out.println("Event detected: " + kind + " on file: " + filePath);
// 根据事件类型进行相应处理
if (kind == StandardWatchEventKinds.ENTRY_CREATE) {
// 新应用被添加,加载该应用
deployApplication(filePath);
} else if (kind == StandardWatchEventKinds.ENTRY_MODIFY) {
// 修改了应用,重新加载或更新
reloadApplication(filePath);
} else if (kind == StandardWatchEventKinds.ENTRY_DELETE) {
// 应用被删除,卸载应用
undeployApplication(filePath);
}
}
// 重置 key 以继续监控
boolean valid = key.reset();
if (!valid) {
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
// 部署新应用
private void deployApplication(Path filePath) {
System.out.println("Deploying new application: " + filePath);
// 实际应用加载逻辑
// 例如加载 Servlet、web.xml 配置等
}
// 重新加载应用
private void reloadApplication(Path filePath) {
System.out.println("Reloading application: " + filePath);
// 重新加载应用的 Servlet 和配置
try {
HttpServer.context = new StandardContext("/conf/web.xml");
HttpServer.context.start();
} catch (Exception e) {
e.printStackTrace();
}
}
// 卸载应用
private void undeployApplication(Path filePath) {
System.out.println("Undeploying application: " + filePath);
// 卸载应用,释放资源
}
}
15.1.3 主要方法解析
startDeploymentMonitor:启动文件监控线程,持续监控应用目录中的变化。
deployApplication:当检测到新应用被创建时,执行部署操作,加载应用。
reloadApplication:当检测到应用被修改时,执行重新加载操作,更新配置或类文件。
undeployApplication:当检测到应用被删除时,执行卸载操作,移除应用及其资源。
15.2 web.xml 文件的重新加载
在热部署过程中,web.xml
文件的更新是非常重要的一部分。我们需要在 web.xml
文件被修改后重新加载它,以使新的配置生效。
15.2.1 实现 web.xml 重新加载
// 重新加载应用
private void reloadApplication(Path filePath) {
System.out.println("Reloading application: " + filePath);
// 重新加载应用的 Servlet 和配置
try {
HttpServer.context = new StandardContext(WEB_XML);
HttpServer.context.start();
} catch (Exception e) {
e.printStackTrace();
}
}
15.2.2 主要方法解析
reloadApplication:重新加载
web.xml
文件,并解析其中的配置,更新 Servlet 的路由和初始化参数。
15.3 集成文件监控与配置热加载
我们将 HotDeployment 组件和 StandardContext 结合,完成整个热部署过程。
15.3.1 集成示例
package com.daicy.minitomcat;
import com.daicy.minitomcat.core.StandardContext;
import com.daicy.minitomcat.servlet.ServletContextImpl;
import javax.servlet.*;
/**
* @author 代长亚
* 一个简单的 HTTP 服务器,用于处理 GET 请求并返回静态文件。
*/
public class HttpServer {
// 静态文件根目录
static final String WEB_ROOT = "webroot";
public static final String CONF_PATH = "/conf";
public static final String WEB_XML = CONF_PATH+ "/web.xml";
public static ServletContextImpl servletContext = new ServletContextImpl();
public static StandardContext context;
public static HotDeployment hotDeployment = new HotDeployment();
public static FilterManager filterManager = new FilterManager();
private static ServletContextListenerManager servletContextListenerManager = new ServletContextListenerManager();
public static HttpSessionListenerManager sessionListenerManager = new HttpSessionListenerManager();
public static void main(String[] args) throws Exception {
Thread daemonThread = new Thread(() -> {
hotDeployment.startDeploymentMonitor();
});
// 将线程设置为守护线程,必须在启动线程之前调用
daemonThread.setDaemon(true);
daemonThread.start();
servletContextListenerManager.addListener(new ServletContextListenerImpl());
sessionListenerManager.addListener(new HttpSessionListenerImpl());
// 启动监听器
servletContextListenerManager.notifyContextInitialized(new ServletContextEvent(servletContext));
context = new StandardContext(WEB_XML);
context.start();
filterManager.addFilter(new LoggingFilter());
HttpConnector connector = new HttpConnector();
connector.start();
// 模拟服务器关闭
Runtime.getRuntime().addShutdownHook(new Thread(HttpServer::stop));
}
public static void stop() {
try {
LogManager.getLogger().info("Server stopping...");
context.stop();
servletContextListenerManager.notifyContextDestroyed(new ServletContextEvent(servletContext));
SessionManager.removeSession();
} catch (Exception e) {
e.printStackTrace();
}
}
}
15.4 学习收获
通过实现热加载和自动部署机制,我们学习了以下内容:
目录监控和事件处理:我们使用 Java NIO 的
WatchService
实现了对文件系统的实时监控,能够及时响应目录中的变化。热部署与应用更新:通过监控目录变化,我们实现了在不重启服务器的情况下自动加载、更新或卸载应用。这为现代 Web 容器提供了动态部署的能力。
web.xml 文件的重新加载:我们实现了
web.xml
配置文件的重新加载机制,能够在配置发生变化时自动更新容器的行为。
这些实现使得我们的 Web 容器在开发和生产环境中都具有高度的灵活性和动态扩展能力,可以支持快速迭代和应用的持续部署。
项目源代码地址:
https://github.com/daichangya/MiniTomcat/tree/chapter15/mini-tomcat