这段代码是一个简化版的 Tomcat 服务器实现,核心是模拟 Tomcat 接收 HTTP 请求、路由到对应 Servlet、执行 Servlet 逻辑并返回响应的过程。主要流程如下:

  1. 启动服务器Tomcat 类的 main 方法启动,先部署 Web 应用(加载 webapps 下的应用及其中的 Servlet),再启动 ServerSocket 监听 8080 端口。
  2. 接收请求:当客户端(如浏览器)连接时,ServerSocket 接收 Socket,并交给线程池中的 SocketProcessor 处理。
  3. 解析请求SocketProcessor 从 Socket 中读取 HTTP 请求数据,解析出请求方法(如 GET)、URL、协议版本,构造 Request 对象。
  4. 路由到 Servlet:根据请求 URL 中的应用名和 Servlet 路径,从 Context(对应 Web 应用)的 Servlet 映射表中找到目标 Servlet
  5. 执行 Servlet 逻辑:调用 Servlet 的 service 方法(最终会执行 doGet/doPost 等方法),Servlet 处理业务逻辑并向 Response 写入响应数据。
  6. 返回响应Response 把响应行、响应头、响应体按 HTTP 协议格式写入 Socket 的输出流,返回给客户端.

一、核心类实现逻辑与原理详解

1. AbstractHttpServletRequest 类
package com.example;
// 导入Servlet相关接口和工具类
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.Principal;
import java.util.*;

// 抽象类实现HttpServletRequest接口,作为请求对象的基础
public class AbstractHttpServletRequest implements HttpServletRequest {
    // 以下所有方法均为接口方法的空实现
    @Override
    public String getAuthType() { return ""; }  // 返回认证类型,默认空实现
    
    @Override
    public Cookie[] getCookies() { return new Cookie[0]; }  // 返回Cookie数组,默认空数组
    
    @Override
    public long getDateHeader(String s) { return 0; }  // 返回日期类型头,默认0
    
    // ... 其余方法均为类似的空实现
}

原理:作为HttpServletRequest接口的适配器,提供基础实现框架。实际请求信息由子类(如Request)通过重写关键方法提供,避免子类需要实现所有接口方法,符合适配器设计模式

2. AbstractHttpServletResponse 类
package com.example;
// 导入相关类
import javax.servlet.ServletOutputStream;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
// ... 其他导入

// 抽象类实现HttpServletResponse接口,作为响应对象的基础
public class AbstractHttpServletResponse implements HttpServletResponse {
    // 继承Object类的方法
    @Override
    public int hashCode() { return super.hashCode(); }  // 默认哈希实现
    
    // 以下为HttpServletResponse接口方法的空实现
    @Override
    public void addCookie(Cookie cookie) {}  // 添加Cookie,空实现
    
    @Override
    public boolean containsHeader(String s) { return false; }  // 检查是否包含指定头,默认false
    
    // ... 其余方法均为类似的空实现
}

原理:与请求抽象类类似,为响应对象提供基础框架。子类(如Response)重写关键方法实现响应构建逻辑,减少代码冗余。

3. Context 类
package com.example;
import javax.servlet.Servlet;
import java.util.HashMap;
import java.util.Map;

// 表示Web应用上下文,管理URL与Servlet的映射
public class Context {
    private String appName;  // 应用名称
    // URL模式与Servlet的映射表
    private Map<String, Servlet> urlPatternMapping = new HashMap<>();

    public Context(String appName) { this.appName = appName; }  // 构造方法初始化应用名
    
    // 添加URL模式与Servlet的映射
    public void addUrlPatternMapping(String urlPattern, Servlet servlet) {
        urlPatternMapping.put(urlPattern, servlet);
    }
    
    // 根据URL模式查找对应的Servlet
    public Servlet getByUrlPattern(String urlPattern) {
        for (String key : urlPatternMapping.keySet()) {
            if (key.contains(urlPattern)) {  // 简单匹配逻辑
                return urlPatternMapping.get(key);
            }
        }
        return null;
    }
}

原理:模拟 Tomcat 中的Context组件,负责管理单个 Web 应用的 Servlet 映射关系。通过urlPatternMapping存储 URL 路径与 Servlet 的对应关系,实现请求路由功能。

4. DefaultServlet 类
// 继承 HttpServlet,作为默认 Servlet(当请求未匹配到其他 Servlet 时使用)
public class DefaultServlet extends HttpServlet {
    // 无自定义逻辑,使用 HttpServlet 的默认行为
}

作用:当请求的 URL 模式未匹配到任何已注册的 Servlet 时,使用该默认 Servlet 处理请求。

5. Request 类
package com.example;
import javax.servlet.http.HttpServletRequest;
import java.net.Socket;

// 具体请求实现类,封装HTTP请求信息
public class Request extends AbstractHttpServletRequest {
    private String method;  // 请求方法(GET/POST等)
    private String url;  // 请求URL
    private String protocol;  // 协议版本(HTTP/1.1)
    private Socket socket;  // 底层通信Socket

    // 构造方法初始化核心属性
    public Request(String method, String url, String protocol, Socket socket) {
        this.method = method;
        this.url = url;
        this.protocol = protocol;
        this.socket = socket;
    }
    
    // 重写方法返回请求方法
    @Override
    public String getMethod() { return method; }
    
    // 重写方法返回请求URL
    @Override
    public StringBuffer getRequestURL() { return new StringBuffer(url); }
    
    // 重写方法返回协议版本
    @Override
    public String getProtocol() { return protocol; }
    
    // 获取底层Socket
    public Socket getSocket() { return socket; }
}

原理:封装从 Socket 中解析出的 HTTP 请求核心信息(方法、URL、协议),提供给后续 Servlet 处理使用。通过继承抽象类,只需要实现关键方法,简化代码。

6.Response 类
package com.example;
import javax.servlet.ServletOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;

// 具体响应实现类,负责构建和发送HTTP响应
public class Response extends AbstractHttpServletResponse{
    private int status = 200;  // 响应状态码,默认200
    private byte SP = ' ';  // 空格分隔符
    private byte CR = '\r';  // 回车符
    private byte LF = '\n';  // 换行符
    private String message = "OK";  // 状态信息
    private Map<String,String> headers = new HashMap<>();  // 响应头
    private Request request;  // 关联的请求对象
    private OutputStream socketOutputStream;  // Socket输出流
    private ResponseServletOutputStream responseServletOutputStream = new ResponseServletOutputStream();  // 响应体输出流

    // 构造方法初始化请求和输出流
    public Response(Request request) {
        this.request = request;
        try {
            this.socketOutputStream = request.getSocket().getOutputStream();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    // 设置响应状态码和信息
    @Override
    public void setStatus(int i, String s) {
        status = i;
        message = s;
    }

    // 添加响应头
    @Override
    public void addHeader(String s, String s1) {
        headers.put(s, s1);
    }

    // 获取响应体输出流
    @Override
    public ResponseServletOutputStream getOutputStream() throws IOException {
        return new ResponseServletOutputStream();
    }

    // 完成响应并发送
    public void complete() throws IOException {
        sendResponseLine();  // 发送响应行
        sendResponseHeader();  // 发送响应头
        sendResponseBody();  // 发送响应体
    }

    // 发送响应体
    private void sendResponseBody() throws IOException {
        socketOutputStream.write(getOutputStream().getBytes());
    }

    // 发送响应头
    private void sendResponseHeader() throws IOException {
        // 自动补充必要的响应头
        if (!headers.containsKey("Content-Length")) {
            addHeader("Content-Length", String.valueOf(getOutputStream().getPos()));
        }
        if (!headers.containsKey("Content-Type")) {
            addHeader("Content-Type", "text/plain;charset=UTF-8");
        }
        // 写入所有响应头
        for (Map.Entry<String, String> entry : headers.entrySet()) {
            socketOutputStream.write(entry.getKey().getBytes());
            socketOutputStream.write(":".getBytes());
            socketOutputStream.write(entry.getValue().getBytes());
            socketOutputStream.write(CR);
            socketOutputStream.write(LF);
        }
        socketOutputStream.write(CR);
        socketOutputStream.write(LF);  // 响应头结束标志
    }

    // 发送响应行
    private void sendResponseLine() throws IOException {
        socketOutputStream.write(request.getProtocol().getBytes());
        socketOutputStream.write(SP);
        socketOutputStream.write(status);  // 注意:此处有bug,状态码应转为字符串
        socketOutputStream.write(SP);
        socketOutputStream.write(message.getBytes());
        socketOutputStream.write(CR);
        socketOutputStream.write(LF);
    }
}

原理:按照 HTTP 协议规范构建响应内容(响应行→响应头→响应体),并通过 Socket 输出流发送给客户端。核心是维护响应状态、响应头和响应体的结构,确保符合 HTTP 格式要求。

7. ResponseServletOutputStream 类
// 实现 javax.servlet.ServletOutputStream,用于写入响应体数据
public class ResponseServletOutputStream extends ServletOutputStream {
    private byte[] bytes = new byte[1024]; // 存储响应体数据的字节数组
    private int pos = 0; // 数据写入位置指针

    @Override
    public void write(int b) throws IOException {
        bytes[pos] = (byte) b; // 将数据写入字节数组
        pos++;
    }
    // 获取存储响应体的字节数组
    public byte[] getBytes() { return bytes; }
    // 获取已写入响应体数据的长度
    public int getPos() { return pos; }
    // 以下为 ServletOutputStream 接口方法的空实现(非关键逻辑)
    @Override
    public boolean isReady() { return false; }
    @Override
    public void setWriteListener(WriteListener writeListener) {}
}

作用:作为响应体的输出流,暂存响应体数据,供 Response 类在发送响应时使用。

8. SocketProcessor 类

package com.example;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;

// 处理Socket连接的线程任务类
public class SocketProcessor implements Runnable {
    private Socket socket;  // 待处理的Socket
    private Tomcat tomcat;  // Tomcat实例引用

    public SocketProcessor(Socket socket, Tomcat tomcat) {
        this.socket = socket;
        this.tomcat = tomcat;
    }

    // 线程执行入口
    @Override
    public void run() {
        processSocket(socket);  // 处理Socket连接
    }

    // 处理Socket连接的核心逻辑
    private void processSocket(Socket socket) {
        try {
            InputStream inputStream = socket.getInputStream();  // 获取输入流
            byte[] bytes = new byte[1024];
            inputStream.read(bytes);  // 读取请求数据

            // 解析请求方法(GET/POST)
            int pos = 0, begin = 0, end = 0;
            for (; pos < bytes.length; pos++, end++) {
                if (bytes[pos] == ' ') break;  // 空格分隔请求方法和URL
            }
            StringBuilder method = new StringBuilder();
            for (; begin < end; begin++) {
                method.append((char) bytes[begin]);
            }

            // 解析URL
            pos++;
            begin++;
            end++;
            for (; pos < bytes.length; pos++, end++) {
                if (bytes[pos] == ' ') break;  // 空格分隔URL和协议
            }
            StringBuilder url = new StringBuilder();
            for (; begin < end; begin++) {
                url.append((char) bytes[begin]);
            }

            // 解析协议版本
            pos++;
            begin++;
            end++;
            for (; pos < bytes.length; pos++, end++) {
                if (bytes[pos] == '\r') break;  // 回车符结束协议版本
            }
            StringBuilder protocol = new StringBuilder();
            for (; begin < end; begin++) {
                protocol.append((char) bytes[begin]);
            }

            // 创建请求和响应对象
            Request request = new Request(method.toString(), url.toString(), protocol.toString(), socket);
            Response response = new Response(request);

            // 路由到对应的Servlet
            String requestUrl = request.getRequestURL().toString().substring(1);
            String[] parts = requestUrl.split("/");
            String appName = parts[0];  // 提取应用名
            Context context = tomcat.getContextMap().get(appName);  // 获取应用上下文

            if (parts.length > 1) {
                Servlet servlet = context.getByUrlPattern(parts[1]);  // 查找匹配的Servlet
                if (servlet != null) {
                    servlet.service(request, response);  // 执行Servlet逻辑
                    response.complete();  // 发送响应
                } else {
                    new DefaultServlet().service(request, response);  // 使用默认Servlet
                }
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

原理:作为线程任务,负责处理单个 Socket 连接的完整生命周期:

  1. 从 Socket 输入流读取 HTTP 请求数据
  2. 解析请求方法、URL、协议版本等核心信息
  3. 创建RequestResponse对象
  4. 根据 URL 路由到对应的 Web 应用(Context)和 Servlet
  5. 调用 Servlet 处理逻辑并发送响应

采用线程池 + 任务队列模式(在Tomcat类中实现),支持并发处理多个连接。

9. Tomcat 类
package com.example;
import javax.servlet.Servlet;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

// 模拟Tomcat服务器的核心类
public class Tomcat {
    private Map<String, Context> contextMap = new HashMap<>();  // 应用上下文映射表

    // 启动服务器
    public void start() {
        try {
            ExecutorService executorService = Executors.newFixedThreadPool(20);  // 创建线程池
            ServerSocket serverSocket = new ServerSocket(8080);  // 监听8080端口
            while (true) {  // 循环接受连接
                Socket socket = serverSocket.accept();  // 接受客户端连接
                executorService.execute(new SocketProcessor(socket, this));  // 提交任务到线程池
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public static void main(String[] args) {
        Tomcat tomcat = new Tomcat();
        tomcat.deployApps();  // 部署应用
        tomcat.start();  // 启动服务器
    }

    // 部署webapps目录下的所有应用
    private void deployApps() {
        File webapps = new File(System.getProperty("user.dir"), "webapps");  // 获取webapps目录
        for (String app : webapps.list()) {  // 遍历应用目录
            deployApp(webapps, app);
        }
    }

    // 部署单个应用
    private void deployApp(File webapps, String appName) {
        Context context = new Context(appName);  // 创建应用上下文
        File appDirectory = new File(webapps, appName);
        File classesDirectory = new File(appDirectory, "classes");  // 类文件目录

        List<File> files = getAllFilesPath(classesDirectory);  // 获取所有类文件
        for (File clazz : files) {
            // 解析类的全限定名
            String name = clazz.getPath();
            name = name.replace(classesDirectory.getPath() + "\\", "");
            name = name.replace(".class", "");
            name = name.replace("\\", ".");

            try {
                // 使用自定义类加载器加载类
                WebappClassLoader classLoader = new WebappClassLoader(new URL[]{classesDirectory.toURL()});
                Class<?> servletClass = classLoader.loadClass(name);

                // 判断是否为Servlet
                if (HttpServlet.class.isAssignableFrom(servletClass)) {
                    // 处理@WebServlet注解
                    if (servletClass.isAnnotationPresent(WebServlet.class)) {
                        WebServlet annotation = servletClass.getAnnotation(WebServlet.class);
                        String[] urlPatterns = annotation.urlPatterns();  // 获取URL模式

                        // 注册Servlet映射
                        for (String urlPattern : urlPatterns) {
                            context.addUrlPatternMapping(urlPattern, (Servlet) servletClass.newInstance());
                        }
                    }
                }
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
        contextMap.put(appName, context);  // 保存应用上下文
    }

    // 递归获取目录下所有文件
    public List<File> getAllFilesPath(File srcFile) {
        List<File> result = new ArrayList<>();
        File[] files = srcFile.listFiles();
        if (files != null) {
            for (File file : files) {
                if (file.isDirectory()) {
                    result.addAll(getAllFilesPath(file));  // 递归处理子目录
                } else {
                    result.add(file);  // 添加文件
                }
            }
        }
        return result;
    }

    public Map<String, Context> getContextMap() { return contextMap; }
}

原理:模拟 Tomcat 服务器的核心功能:

  1. 启动服务:创建ServerSocket监听 8080 端口,通过线程池处理并发连接
  2. 应用部署:扫描webapps目录下的应用,加载classes目录中的 Servlet 类
  3. 类加载:使用自定义WebappClassLoader加载应用类,模拟 Tomcat 的类加载机制
  4. Servlet 注册:解析@WebServlet注解,建立 URL 模式与 Servlet 的映射关系

通过contextMap管理多个 Web 应用,实现类似 Tomcat 的多应用部署能力。

10.WebappClassLoader
package com.example;

import java.net.URL;
import java.net.URLClassLoader;

//自定义类加载器
public class WebappClassLoader extends URLClassLoader {
    public WebappClassLoader(URL[] urls) {
        super(urls);
    }
}

原理:

  1. 类加载器的基本作用类加载器是 JVM 用于加载 .class 文件到内存并生成 Class 对象的组件。Java 默认的类加载机制遵循双亲委派模型:当加载一个类时,先委托父类加载器加载,只有父类加载器无法加载时,才自己加载。

    但 Tomcat 为了实现Web 应用间的类隔离(例如不同应用可以使用同一类的不同版本),需要自定义类加载器打破这种默认机制。

  2. 继承 URLClassLoader 的原因URLClassLoader 是 JDK 提供的便捷类加载器实现,支持从指定的 URL 路径(如文件系统目录、JAR 包)加载类。对于 Web 应用,类文件通常存放在 WEB-INF/classes 目录或 WEB-INF/lib 下的 JAR 中,正好可以通过 URL 路径指定,因此继承 URLClassLoader 可以简化实现。

  3. Web 应用类隔离的实现在示例代码的 Tomcat 类中,每个 Web 应用(Context)会创建一个独立的 WebappClassLoader 实例,其构造参数 URL[] urls 指向该应用的 classes 目录(如 webapps/myapp/classes)。

    这样做的效果是:

    • 不同 Web 应用的类由各自的 WebappClassLoader 加载,即使类名完全相同(如 com.example.User),也会被 JVM 视为不同的类(因为类的唯一性由「类全限定名 + 类加载器」共同决定)。
    • 避免了不同应用之间的类冲突,例如 A 应用使用 log4j-1.x.jar,B 应用使用 log4j-2.x.jar 不会互相干扰。
  4. 与 Tomcat 实际类加载机制的对应实际 Tomcat 中的类加载器层级更复杂(如 CommonClassLoaderCatalinaClassLoaderSharedClassLoader 等),但核心思想一致:

    • 每个 Web 应用有独立的 WebappClassLoader
    • 优先加载应用自身的类(打破双亲委派),再委托父类加载器加载公共类(如 JRE 核心类、Tomcat 自身类)。

    示例中的 WebappClassLoader 是简化版,仅实现了最基础的「从指定路径加载类」功能,未实现完整的双亲委派打破逻辑,但保留了核心设计思想。

总结:WebappClassLoader 的核心作用是为每个 Web 应用提供独立的类加载器,通过指定类加载路径(classes 目录),实现不同应用间的类隔离,避免类冲突。这是 Tomcat 支持多应用部署的关键机制之一,而继承 URLClassLoader 则简化了从文件系统加载类的实现。

11.Servlet 类(业务示例)
package com.example;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

// 示例Servlet,处理/test请求
@WebServlet(urlPatterns = {"/test"})  // 映射URL模式
public class Servlet extends HttpServlet {
    public Servlet() {}  // 构造方法

    // 处理GET请求
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println(req.getMethod());  // 打印请求方法
        resp.getOutputStream().write("hello wanting".getBytes());  // 写入响应内容
    }
}

原理:业务逻辑处理类,通过@WebServlet注解声明映射的 URL 模式。继承HttpServlet,重写doGet方法处理 GET 请求,向响应流写入数据。

二、整体运行流程总结

  1. 启动阶段Tomcat类的main方法调用deployApps()部署应用,扫描webapps目录加载 Servlet 并建立映射关系,然后启动ServerSocket监听 8080 端口。
  2. 请求处理阶段
    • 客户端连接时,ServerSocket接受Socket并交给线程池的SocketProcessor处理
    • SocketProcessor解析 HTTP 请求,创建RequestResponse对象
    • 根据 URL 路由到对应 Web 应用的 Servlet,调用其service方法
  3. 响应阶段:Servlet 处理逻辑后,Response按 HTTP 协议格式组织响应内容,通过 Socket 发送给客户端。

该实现模拟了 Tomcat 的核心功能:请求解析、Servlet 路由、响应构建,简化了实际 Tomcat 中的复杂组件(如 Connector、Engine 等),但保留了核心工作原理。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐