本章目标

本章将实现 Tomcat 容器中的两个核心组件:WrapperContext

  • 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 服务器中,我们将 StandardContextStandardWrapper 结合,管理多个 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())。为了保持与原文代码语义一致,此处未做修改,实际开发中请注意版本兼容性。