手写Tomcat

发布于:2025-03-09 ⋅ 阅读:(15) ⋅ 点赞:(0)
  1. Tomcat是什么?

Tomcat 是一个开源的、轻量级的 Servlet 容器,也被称为 Web 服务器,由 Apache 软件基金会的 Jakarta 项目开发,在 Java Web 开发领域应用广泛

1)Servlet 容器:Servlet 是 Java 语言编写的服务器端程序,Tomcat 负责管理 Servlet 的生命周期,接收客户端的请求,将请求分发给相应的 Servlet 进行处理,并将 Servlet 处理后的结果返回给客户端。

2)Web 服务器:它能够处理 HTTP 请求,支持静态资源(如 HTML、CSS、JavaScript 文件等)的访问,也能动态生成网页内容。

如下为其图标)

 

2.Tomcat结构

  1. bin目录存 tomcat基本命令
  2. conf目录放置配置文件
  3. lib目录主要用来存放tomcat运行需要加载的jar包
  4. logs目录存放主动启动tomcat的日志
  5. temp目录存放临时文件
  6. Work目录存放 tomcat编译时产生的文件
  7. webapps目录存放我们写的web项目

3.手写Tomcat

手写模拟 Tomcat 程序旨在接收客户端的 HTTP 请求,依据请求的 URL 找到对应的 Servlet 进行处理,并将处理结果以 HTTP 响应的形式返回给客户端。程序通过多个类、接口和注解的协同运作来达成这一功能。

4.在IDEA里手写Tomcat模拟程序的步骤:

1)首先在IDEA里建一个新项目,选择Maven项目(Maven 提供了强大的依赖管理功能,然后在src包中的java包中再创建com.qcby软件包。(如下图)

2)在IDEA里写一个tomcat模拟程序,建一个新项目之后首先建了一个com.qcby软件包 然后在com.qcby包中建了Util,webapps两个包和mytomcat,servletconfigmapping两个类,在util中有responseutil,searchclassutil两个类和webservlet两个注解,在在webapps包中还有三个软件包分别是myweb,req和servlet,myweb中有两个类loginservlet和showservlet,req包中有两个httpservletrequest和httpservletresponse类,在servlet包中有generikservlet,httpservlet两个类和一个servlet接口。(如下图)

5.类、接口和注解的详细信息及关系

1. 注解:WebServlet

  • 定义位置com.qcby.Utill.WebServlet
  • 作用:用于标记一个类为 Servlet 类,并指定该 Servlet 映射的 URL 路径。此为自定义注解,模仿了 Java Servlet 规范中的@WebServlet注解。
  • 与其他组件的关系:被ServletConfigMapping类用于扫描和识别 Servlet 类及其对应的 URL 映射。
  • 代码示例

java

package com.qcby.Utill;

import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;

@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)public @interface WebServlet {

    String value();}

2. 工具类:SearchClassUtil

  • 定义位置com.qcby.Utill.SearchClassUtil
  • 作用:扫描指定包下的所有类,找出使用了WebServlet注解的类。借助 Java 的反射机制和类加载器来实现类的扫描与查找。
  • 与其他组件的关系:为ServletConfigMapping类提供服务,帮助其找到所有被注解的 Servlet 类。
  • 代码示例

java

package com.qcby.Utill;

import java.io.File;import java.io.IOException;import java.net.URL;import java.util.ArrayList;import java.util.Enumeration;import java.util.List;

public class SearchClassUtil {

    public static List<Class<?>> scanClasses(String packageName) throws IOException, ClassNotFoundException {

        List<Class<?>> classes = new ArrayList<>();

        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();

        String path = packageName.replace('.', '/');

        Enumeration<URL> resources = classLoader.getResources(path);

        while (resources.hasMoreElements()) {

            URL resource = resources.nextElement();

            File directory = new File(resource.getFile());

            if (directory.exists()) {

                findClasses(directory, packageName, classes);

            }

        }

        return classes;

    }

    private static void findClasses(File directory, String packageName, List<Class<?>> classes) throws ClassNotFoundException {

        File[] files = directory.listFiles();

        if (files != null) {

            for (File file : files) {

                if (file.isDirectory()) {

                    findClasses(file, packageName + "." + file.getName(), classes);

                } else if (file.getName().endsWith(".class")) {

                    String className = packageName + '.' + file.getName().substring(0, file.getName().length() - 6);

                    classes.add(Class.forName(className));

                }

            }

        }

    }

    public static List<Class<?>> findAnnotatedClasses(String packageName) throws IOException, ClassNotFoundException {

        List<Class<?>> allClasses = scanClasses(packageName);

        List<Class<?>> annotatedClasses = new ArrayList<>();

        for (Class<?> clazz : allClasses) {

            if (clazz.isAnnotationPresent(WebServlet.class)) {

                annotatedClasses.add(clazz);

            }

        }

        return annotatedClasses;

    }}

3. 接口:Servlet

  • 定义位置com.qcby.webapps.servlet.Servlet
  • 作用:定义 Servlet 的基本生命周期方法,如init(初始化)、service(处理请求)和destroy(销毁)。所有的 Servlet 类都需要实现这个接口。
  • 与其他组件的关系GenericServletHttpServlet都实现了这个接口,为具体的 Servlet 类提供统一的规范。
  • 代码示例

java

package com.qcby.webapps.servlet;

import com.qcby.webapps.req.HttpServletRequest;import com.qcby.webapps.req.HttpServletResponse;import java.io.IOException;

public interface Servlet {

    void init();

    void service(HttpServletRequest request, HttpServletResponse response) throws IOException;

    void destroy();}

4. 抽象类:GenericServlet

  • 定义位置com.qcby.webapps.servlet.GenericServlet
  • 作用:实现了Servlet接口,提供了一些通用的 Servlet 功能和方法的默认实现。它是一个抽象类,为具体的 Servlet 类提供了一个基础框架。
  • 与其他组件的关系:继承自Servlet接口,HttpServlet继承自GenericServlet
  • 代码示例

java

package com.qcby.webapps.servlet;

import com.qcby.webapps.req.HttpServletRequest;import com.qcby.webapps.req.HttpServletResponse;import java.io.IOException;

public abstract class GenericServlet implements Servlet {

    @Override

    public void init() {

        System.out.println("初始化servlet....");

    }

    @Override

    public abstract void service(HttpServletRequest request, HttpServletResponse response) throws IOException;

    @Override

    public void destroy() {

        System.out.println("实现servlet对象的销毁....");

    }}

5. 具体类:HttpServlet

  • 定义位置com.qcby.webapps.servlet.HttpServlet
  • 作用:继承自GenericServlet,专门用于处理 HTTP 请求。它可以根据不同的 HTTP 请求方法(如 GET、POST)进行不同的处理。
  • 与其他组件的关系:继承自GenericServlet,具体的 Servlet 类(如LoginServletShowServlet)可以继承HttpServlet类来处理 HTTP 请求。
  • 代码示例

java

package com.qcby.webapps.servlet;

import com.qcby.webapps.req.HttpServletRequest;import com.qcby.webapps.req.HttpServletResponse;import java.io.IOException;

public abstract class HttpServlet extends GenericServlet {

    @Override

    public void service(HttpServletRequest request, HttpServletResponse response) throws IOException {

        String method = request.getMethod();

        if ("GET".equalsIgnoreCase(method)) {

            doGet(request, response);

        } else if ("POST".equalsIgnoreCase(method)) {

            doPost(request, response);

        }

    }

    protected abstract void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException;

    protected abstract void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException;}

6. 请求和响应类:HttpServletRequest 和 HttpServletResponse

  • 定义位置com.qcby.webapps.req.HttpServletRequest 和 com.qcby.webapps.req.HttpServletResponse
  • 作用HttpServletRequest封装了客户端的 HTTP 请求信息,如请求方法、请求路径、请求参数等;HttpServletResponse封装了服务器的响应信息,用于向客户端返回响应内容。
  • 与其他组件的关系:在Servlet接口的service方法中作为参数传递,供具体的 Servlet 类处理请求和生成响应。
  • 代码示例

java

// HttpServletRequestpackage com.qcby.webapps.req;

import java.util.HashMap;import java.util.Map;

public class HttpServletRequest {

    private String path;

    private String method;

    private Map<String, String> parameters = new HashMap<>();

    public HttpServletRequest() {

    }

    public String getPath() {

        return path;

    }

    public void setPath(String path) {

        this.path = path;

    }

    public String getMethod() {

        return method;

    }

    public void setMethod(String method) {

        this.method = method;

    }

    public void setParameter(String name, String value) {

        parameters.put(name, value);

    }

    public String getParameter(String name) {

        return parameters.get(name);

    }

    public void parseParameters(String requestBody) {

        if (requestBody != null && !requestBody.isEmpty()) {

            String[] pairs = requestBody.split("&");

            for (String pair : pairs) {

                String[] keyValue = pair.split("=");

                if (keyValue.length == 2) {

                    setParameter(keyValue[0], keyValue[1]);

                }

            }

        }

    }}

// HttpServletResponsepackage com.qcby.webapps.req;

import java.io.IOException;import java.io.OutputStream;

public class HttpServletResponse {

    private OutputStream outputStream;

    public HttpServletResponse(OutputStream outputStream) {

        this.outputStream = outputStream;

    }

    public void sendResponse(String s) throws IOException {

        String response = "HTTP/1.1 200 OK\r\n" +

                "Content-Type: text/html\r\n" +

                "Content-Length: " + s.length() + "\r\n" +

                "\r\n" +

                s;

        outputStream.write(response.getBytes());

        outputStream.flush();

    }}

7. 配置管理类:ServletConfigMapping

  • 定义位置com.qcby.ServletConfigMapping
  • 作用:管理 Servlet 的配置信息,将 Servlet 类和其映射的 URL 路径进行关联。它利用SearchClassUtil扫描指定包下的 Servlet 类,并根据WebServlet注解的信息建立映射关系。
  • 与其他组件的关系:依赖SearchClassUtil来获取 URL 路径被注解的 Servlet 类,依赖WebServlet注解来获取 URL 路径,为MyTomcat提供 Servlet 映射信息。
  • 代码示例

java

package com.qcby;

import com.qcby.Utill.SearchClassUtil;import com.qcby.Utill.WebServlet;import com.qcby.webapps.servlet.HttpServlet;import java.io.IOException;import java.lang.reflect.InvocationTargetException;import java.util.HashMap;import java.util.List;import java.util.Map;

public class ServletConfigMapping {

    public static Map<String, HttpServlet> servletMap = new HashMap<>();

    public static void init(String packageName) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {

        List<Class<?>> annotatedClasses = SearchClassUtil.findAnnotatedClasses(packageName);

        for (Class<?> clazz : annotatedClasses) {

            WebServlet webServlet = clazz.getAnnotation(WebServlet.class);

            if (webServlet != null) {

                String urlPattern = webServlet.value();

                HttpServlet servlet = (HttpServlet) clazz.getDeclaredConstructor().newInstance();

                servletMap.put(urlPattern, servlet);

            }

        }

    }}

8. 具体 Servlet 类:LoginServlet 和 ShowServlet

  • 定义位置com.qcby.webapps.myweb.LoginServlet 和 com.qcby.webapps.myweb.ShowServlet
  • 作用:具体的业务 Servlet 类,继承自HttpServlet,实现具体的业务逻辑。
  • 与其他组件的关系:继承自HttpServlet,通过WebServlet注解的 URL 路径与特定的 URL 进行映射,当客户端请求该 URL 时,对应的 Servlet 类的service方法会被调用进行处理。
  • 代码示例

java

// LoginServletpackage com.qcby.webapps.myweb;

import com.qcby.Utill.WebServlet;import com.qcby.webapps.req.HttpServletRequest;import com.qcby.webapps.req.HttpServletResponse;import com.qcby.webapps.servlet.HttpServlet;import java.io.IOException;

@WebServlet("/login")public class LoginServlet extends HttpServlet {

    @Override

    protected void doGet(HttpServletRequest request, HttpServletResponse response) {

        try {

            response.sendResponse("<html><body><h1>Hello from LoginServlet (GET)</h1></body></html>");

        } catch (IOException e) {

            e.printStackTrace();

        }

    }

    @Override

    protected void doPost(HttpServletRequest request, HttpServletResponse response) {

        try {

            response.sendResponse("<html><body><h1>Hello from LoginServlet (POST)</h1></body></html>");

        } catch (IOException e) {

            e.printStackTrace();

        }

    }}

// ShowServletpackage com.qcby.webapps.myweb;

import com.qcby.Utill.WebServlet;import com.qcby.webapps.req.HttpServletRequest;import com.qcby.webapps.req.HttpServletResponse;import com.qcby.webapps.servlet.HttpServlet;import java.io.IOException;

@WebServlet("/show")public class ShowServlet extends HttpServlet {

    @Override

    protected void doGet(HttpServletRequest request, HttpServletResponse response) {

        try {

            response.sendResponse("<html><body><h1>Hello from ShowServlet (GET)</h1></body></html>");

        } catch (IOException e) {

            e.printStackTrace();

        }

    }

    @Override

    protected void doPost(HttpServletRequest request, HttpServletResponse response) {

        try {

            response.sendResponse("<html><body><h1>Hello from ShowServlet (POST)</h1></body></html>");

        } catch (IOException e) {

            e.printStackTrace();

        }

    }}

9. 核心类:MyTomcat

  • 定义位置com.qcby.MyTomcat
  • 作用:模拟 Tomcat 服务器的核心类,包括启动服务器、接收客户端请求、根据请求的 URL 路径从ServletConfigMapping中找到对应的 Servlet,并调用该 Servlet 的service方法进行请求处理和响应生成。
  • 与其他组件的关系:依赖ServletConfigMapping类获取 Servlet 映射信息,接收HttpServletRequestHttpServletResponse对象进行请求处理和响应返回。
  • 代码示例

java

package com.qcby;

import com.qcby.webapps.req.HttpServletRequest;import com.qcby.webapps.req.HttpServletResponse;import com.qcby.webapps.servlet.HttpServlet;import java.io.IOException;import java.io.InputStream;import java.net.ServerSocket;import java.net.Socket;

public class MyTomcat {

    public static void main(String[] args) {

        try {

            ServletConfigMapping.init("com.qcby.webapps.myweb");

            ServerSocket serverSocket = new ServerSocket(8081);

            System.out.println("MyTomcat started on port 8081");

            while (true) {

                Socket socket = serverSocket.accept();

                InputStream inputStream = socket.getInputStream();

                int count = 0;

                while (count == 0) {

                    count = inputStream.available();

                }

                byte[] bytes = new byte[count];

                inputStream.read(bytes);

                String context = new String(bytes);

                System.out.println(context);

                if (context.equals("")) {

                    System.out.println("你输入了一个空请求");

                } else {

                    String[] lines = context.split("\\r\\n");

                    String firstLine = lines[0];

                    String[] parts = firstLine.split(" ");

                    String method = parts[0];

                    String uri = parts[1];

                    HttpServletRequest request = new HttpServletRequest();

                    request.setPath(uri);

                    request.setMethod(method);

                    String requestBody = null;

                    boolean isBody = false;

                    StringBuilder bodyBuilder = new StringBuilder();

                    for (String line : lines) {

                        if (isBody) {

                            bodyBuilder.append(line);

                        }

                        if (line.isEmpty()) {

                            isBody = true;

                        }

                    }

                    if (bodyBuilder.length() > 0) {

                        requestBody = bodyBuilder.toString();

                        request.parseParameters(requestBody);

                    }

                    HttpServletResponse response = new HttpServletResponse(socket.getOutputStream());

                    if (ServletConfigMapping.servletMap.containsKey(request.getPath())) {

                        HttpServlet servlet = ServletConfigMapping.servletMap.get(request.getPath());

                        servlet.service(request, response);

                    } else {

                        try {

                            response.sendResponse("<html><body><h1>404 Not Found</h1></body></html>");

                        } catch (IOException e) {

                            e.printStackTrace();

                        }

                    }

                }

                socket.close();

            }

        } catch (Exception e) {

            e.printStackTrace();

        }

    }}

关系总结

  • GenericServlet实现了Servlet接口。
  • HttpServlet继承自GenericServlet
  • LoginServletShowServlet继承自HttpServlet
  • ServletConfigMapping依赖SearchClassUtil查找 Servlet 类,依赖WebServlet注解获取 URL 映射。
  • MyTomcat依赖ServletConfigMapping获取 Servlet 映射关系,接收HttpServletRequestHttpServletResponse进行请求处理和响应。
  • WebServlet注解用于LoginServletShowServlet,实现 URL 与 Servlet 类的映射 。

6.

Tomcat 虚拟机工作原理

这个模拟 Tomcat 程序的工作原理可以概括为以下几个步骤:

  1. 启动服务器MyTomcat 类的 main 方法创建一个 ServerSocket 对象,并监听指定的端口(这里是 8081)。
  2. 初始化 Servlet 映射:调用 ServletConfigMapping.init 方法,通过 SearchClassUtil 扫描指定包下的类,找出带有 WebServlet 注解的类,将注解中的 value 属性作为 URL 路径,创建对应的 Servlet 实例,并将 URL 路径和 Servlet 实例的映射关系存储在 servletMap 中。
  3. 接受客户端请求ServerSocket 持续监听客户端的连接请求,当有客户端连接时,通过 accept 方法获取一个 Socket 对象,从该对象的输入流中读取客户端发送的 HTTP 请求信息。
  4. 解析请求信息:将读取到的请求信息解析为 HttpServletRequest 对象,提取请求方法(如 GET、POST)和请求路径。
  5. 查找对应的 Servlet:根据请求路径在 servletMap 中查找对应的 Servlet 实例。
  6. 处理请求:如果找到对应的 Servlet 实例,调用其 service 方法处理请求。HttpServlet 类的 service 方法会根据请求方法调用 doGet 或 doPost 方法。
  7. 发送响应:在 doGet 或 doPost 方法中,根据业务逻辑生成响应内容,通过 HttpServletResponse 对象将响应信息发送给客户端。
  8. 关闭连接:处理完请求后,关闭 Socket 连接,继续监听下一个客户端请求。

类之间的逻辑关系、映射和继承关系

继承关系

  • GenericServlet 实现了 Servlet 接口,提供了 init 和 destroy 方法的默认实现,service 方法为抽象方法,需要子类实现。
  • HttpServlet 继承自 GenericServlet,重写了 service 方法,根据请求方法调用 doGet 或 doPost 方法,doGet 和 doPost 方法为抽象方法,需要具体的 Servlet 类实现。
  • LoginServlet 和 ShowServlet 继承自 HttpServlet,实现了 doGet 和 doPost 方法,处理具体的业务逻辑。
映射关系

  • WebServlet 注解用于将 Servlet 类映射到一个 URL 路径,ServletConfigMapping 类通过扫描带有该注解的类,将 URL 路径和 Servlet 实例的映射关系存储在 servletMap 中。
逻辑关系

  • MyTomcat 类是程序的入口,负责启动服务器、接受客户端请求、解析请求信息、查找对应的 Servlet 实例并调用其 service 方法处理请求。
  • ServletConfigMapping 类负责初始化 Servlet 映射,通过 SearchClassUtil 扫描指定包下的类,找出带有 WebServlet 注解的类,创建对应的 Servlet 实例,并将 URL 路径和 Servlet 实例的映射关系存储在 servletMap 中。
  • SearchClassUtil 类用于扫描指定包下的类,找出带有 WebServlet 注解的类。
  • HttpServletRequest 类用于封装客户端的请求信息,包括请求方法、请求路径和请求参数。
  • HttpServletResponse 类用于封装服务器的响应信息,提供了 sendResponse 方法将响应信息发送给客户端。
  • ResponseUtil 类提供了一些工具方法,用于生成 HTTP 响应头信息。

7.在tomcat中servlet是动态资源;网页前端代码是静态资源

手写完tomcat之后需要配置动态资源和静态资源,才能收到请求

这里以收到servlet的动态资源为重点

配置完动态资源之后在网页搜索localhost://(在MyTomcat定义的端口)8081/show  就会在网页得到请求(如下)

这种情况表明动态资源配置成功了