第十一章:实现 Wrapper 和 Context-MiniTomcat
本章目标
本章将实现 Tomcat 容器中的两个核心组件:Wrapper 和 Context。
- Wrapper:负责管理单个 Servlet 的生命周期,封装 Servlet 的创建、初始化、调用和销毁过程。
- Context:用于管理 Web 应用的上下文。一个 Context 可以包含多个 Wrapper,每个 Wrapper 代表一个 Servlet。Context 负责加载和卸载整个 Web 应用,并维护应用的配置和生命周期。
通过本章的实现,我们将支持在同一个 Web 应用中配置多个 Wrapper,每个 Wrapper 绑定到不同的 Servlet,从而实现多个 Servlet 在同一 Web 应用中共存。
11.1 Wrapper 接口设计
Wrapper 负责管理单个 Servlet 的生命周期。它提供了 Servlet 的初始化、销毁方法,并可以访问和执行该 Servlet。
package com.daicy.minitomcat.core;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public interface Wrapper {
// 初始化 Wrapper,加载 Servlet
void loadServlet() throws ServletException;
// 调用 Servlet 的 service 方法处理请求
void invoke(HttpServletRequest request, HttpServletResponse response) throws Exception;
// 销毁 Servlet
void unloadServlet();
// 获取绑定的 Servlet 实例
Servlet getServlet();
}接口方法说明:
loadServlet():加载并初始化 Servlet。invoke(HttpServletRequest request, HttpServletResponse response):调用 Servlet 的service()方法处理请求。unloadServlet():销毁 Servlet 实例。getServlet():获取当前 Wrapper 绑定的 Servlet 实例。
11.2 Wrapper 实现
StandardWrapper 类实现了 Wrapper 接口,它封装了一个 Servlet 实例,并提供了初始化、调用、销毁等操作。
package com.daicy.minitomcat.core;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class StandardWrapper implements Wrapper {
private Servlet servlet;
private String className;
private ServletConfig servletConfig;
public StandardWrapper(ServletConfig servletConfig, String className) {
this.servletConfig = servletConfig;
this.className = className;
}
@Override
public void loadServlet() throws ServletException {
try {
// 加载 Servlet 实例
servlet = (Servlet) Class.forName(className).newInstance();
servlet.init(servletConfig);
} catch (Exception e) {
throw new ServletException("Failed to load servlet", e);
}
}
@Override
public void invoke(HttpServletRequest request, HttpServletResponse response) throws Exception {
servlet.service(request, response);
}
@Override
public void unloadServlet() {
if (servlet != null) {
servlet.destroy();
}
}
@Override
public Servlet getServlet() {
return servlet;
}
}实现细节说明:
loadServlet():通过反射加载 Servlet 类,并调用init()方法进行初始化。invoke(HttpServletRequest request, HttpServletResponse response):调用 Servlet 的service()方法处理请求。unloadServlet():调用 Servlet 的destroy()方法销毁 Servlet 实例。getServlet():返回当前绑定的 Servlet 实例。
11.3 Context 接口设计
Context 管理着 Web 应用中的所有 Wrapper,它负责加载、初始化和卸载 Web 应用中的 Servlet,并提供相关配置。
package com.daicy.minitomcat.core;
import javax.servlet.ServletException;
import java.util.List;
public interface Context {
// 加载所有 Servlet
void load() throws ServletException;
// 卸载所有 Servlet
void unload();
// 获取 Web 应用中的所有 Wrapper
List<Wrapper> getWrappers();
}接口方法说明:
load():加载所有 Servlet,初始化 Web 应用。unload():卸载所有 Servlet,释放资源。getWrappers():返回当前 Context 中的所有 Wrapper。
11.4 Context 实现
StandardContext 类实现了 Context 接口,管理多个 Wrapper,并提供加载、调用和卸载 Web 应用的功能。
package com.daicy.minitomcat.core;
import com.daicy.minitomcat.HttpServer;
import com.daicy.minitomcat.WebXmlServletContainer;
import com.daicy.minitomcat.servlet.HttpServletResponseImpl;
import com.google.common.collect.Lists;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.*;
import java.util.stream.Collectors;
public class StandardContext implements Context {
private Map<String, Wrapper> wrapperMap = new HashMap<>();
private WebXmlServletContainer config;
public StandardContext(String configFilePath) throws ServletException {
config = new WebXmlServletContainer();
config.loadConfig(configFilePath);
load();
}
public Wrapper getWrapper(String servletPath) {
String servletName = config.getServletName(servletPath);
return getWrapperByName(servletName);
}
public Wrapper getWrapperByName(String servletName) {
return wrapperMap.get(servletName);
}
@Override
public void load() throws ServletException {
Map<String, ServletConfig> servletConfigMap = config.getServletConfigMap();
for (String className : servletConfigMap.keySet()) {
ServletConfig servletConfig = servletConfigMap.get(className);
Wrapper wrapper = new StandardWrapper(servletConfig, className);
wrapper.loadServlet();
wrapperMap.put(servletConfig.getServletName(), wrapper);
}
}
@Override
public void unload() {
List<Wrapper> wrappers = getWrappers();
if (null == wrappers) {
return;
}
wrappers.forEach(Wrapper::unloadServlet);
}
@Override
public List<Wrapper> getWrappers() {
return new ArrayList<>(wrapperMap.values());
}
public List<Servlet> getServlets() {
List<Wrapper> wrappers = getWrappers();
if (null == wrappers) {
return Lists.newArrayList();
}
return wrappers.stream().map(Wrapper::getServlet).collect(Collectors.toList());
}
public List<String> getServletNames() {
return config.getServletNames();
}
}实现细节说明:
load():初始化所有 Wrapper,并加载其对应的 Servlet。unload():卸载所有 Wrapper,销毁其绑定的 Servlet。getWrappers():返回当前 Context 中的所有 Wrapper。
11.5 整合 Wrapper 和 Context
在 Web 服务器中,我们将 StandardContext 和 StandardWrapper 结合,管理多个 Servlet。
package com.daicy.minitomcat;
import com.daicy.minitomcat.core.StandardContext;
import com.daicy.minitomcat.servlet.CustomHttpSession;
import com.daicy.minitomcat.servlet.ServletContextImpl;
import javax.servlet.*;
import javax.servlet.http.HttpSessionEvent;
import java.util.Enumeration;
/**
* @author 代长亚
* 一个简单的 HTTP 服务器,用于处理 GET 请求并返回静态文件。
*/
public class HttpServer {
static final String WEB_ROOT = "webroot"; // 静态文件根目录
public static ServletContextImpl servletContext = new ServletContextImpl();
public static StandardContext context;
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 ServletException {
servletContextListenerManager.addListener(new ServletContextListenerImpl());
sessionListenerManager.addListener(new HttpSessionListenerImpl());
// 启动监听器
servletContextListenerManager.notifyContextInitialized(new ServletContextEvent(servletContext));
context = new StandardContext("/web.xml");
filterManager.addFilter(new LoggingFilter());
HttpConnector connector = new HttpConnector();
connector.start();
// 模拟服务器关闭
Runtime.getRuntime().addShutdownHook(new Thread(HttpServer::stop));
}
public static void stop() {
System.out.println("Server stopping...");
context.unload();
servletContextListenerManager.notifyContextDestroyed(new ServletContextEvent(servletContext));
SessionManager.removeSession();
}
}HttpServer类负责启动 Web 应用,加载 Servlet,处理请求并卸载 Servlet。
11.6 学习收获
通过实现 Wrapper 和 Context,我们学到了以下内容:
- Wrapper:负责管理单个 Servlet 的生命周期。每个 Wrapper 可以独立地加载、调用和销毁 Servlet。
- Context:Web 应用的上下文容器,管理着所有 Wrapper 和 Servlet 的生命周期。Context 使得多个 Servlet 可以在同一个 Web 应用中共存。
- 组件化管理:通过 Wrapper 和 Context,我们将 Servlet 的生命周期管理进行了模块化设计,增强了 Web 应用的可维护性和扩展性。
这一结构有助于我们更好地理解 Web 容器的设计原理,并能灵活地管理和扩展 Web 应用。
项目源代码地址:
https://github.com/daichangya/MiniTomcat/tree/chapter11/mini-tomcat
说明:本文代码示例中使用的Class.forName(className).newInstance()方法在 Java 9 及更高版本中已被标记为 deprecated(推荐使用getDeclaredConstructor().newInstance())。为了保持与原文代码语义一致,此处未做修改,实际开发中请注意版本兼容性。
版权声明:本文为原创文章,版权归 戴老师的博客 所有,转载请联系博主获得授权。
本文地址:https://1diff.fun/archives/di-shi-yi-zhang--shi-xian-wrapper-he-context-minitomcat.html
如果对本文有什么问题或疑问都可以在评论区留言,我看到后会尽量解答。