Tomcat 的伪代码

1) Tomcat 初始化流程

class Tomcat {
    
    
    // 用来存储所有的 Servlet 对象
 private List<Servlet> instanceList = new ArrayList<>();
 public void start() {
    
    
       // 根据约定,读取 WEB-INF/web.xml 配置文件;
        // 并解析被 @WebServlet 注解修饰的类
       
        // 假定这个数组里就包含了我们解析到的所有被 @WebServlet 注解修饰的类. 
        Class<Servlet>[] allServletClasses = ...;
        
        // 这里要做的的是实例化出所有的 Servlet 对象出来;
        for (Class<Servlet> cls : allServletClasses) {
    
    
            // 这里是利用 java 中的反射特性做的
            // 实际上还得涉及一个类的加载问题,因为我们的类字节码文件,是按照约定的
            // 方式(全部在 WEB-INF/classes 文件夹下)存放的,所以 tomcat 内部是
            // 实现了一个自定义的类加载器(ClassLoader)用来负责这部分工作。
            
            Servlet ins = cls.newInstance();
            instanceList.add(ins);
       }
        
        // 调用每个 Servlet 对象的 init() 方法,这个方法在对象的生命中只会被调用这一次;
        for (Servlet ins : instanceList) {
    
    
            ins.init();
       }
        
        // 启动一个 HTTP 服务器
        // 并用线程池的方式分别处理每一个 Request
        ServerSocket serverSocket = new ServerSocket(8080);
        // 实际上 tomcat 不是用的固定线程池,这里只是为了说明情况
        ExecuteService pool = Executors.newFixedThreadPool(100);
        
        while (true) {
    
    
            Socket socket = ServerSocket.accept();
            // 每个请求都是用一个线程独立支持,这里体现了我们 Servlet 是运行在多线程环境下的
            pool.execute(new Runnable() {
    
    
               doHttpRequest(socket); 
           });
       }
        // 调用每个 Servlet 对象的 destroy() 方法,这个方法在对象的生命中只会被调用这一次;
        for (Servlet ins : instanceList) {
    
    
            ins.destroy();
       }
   }
    
    public static void main(String[] args) {
    
    
        new Tomcat().start();
   }
}

步骤:

  1. main()调用start()方法
  2. 解析所有被 @WebServlet 注解修饰的类,放到一个数组中
  3. 用一个ArrayList存储 Servlet 对象,利用反射机制实例化所有解析出来的类中(数组中)的servlet对象,并保存到ArrayList中
  4. 把每个 servlet 对象初始化(调用inti()方法)
  5. 启动一个 HTTP 服务器(new ServerSocket(8080))
  6. 创建一个线程池,然后接受请求并执行(while(ture))
  7. 最后,将每个 servlet 对象销毁(调用destroy() 方法)

**小结 **

  • Tomcat 的代码中内置了 main 方法. 当我们启动 Tomcat 的时候, 就是从 Tomcat 的 main 方法开始执行的.
  • 被 @WebServlet 注解修饰的类会在 Tomcat 启动的时候就被获取到, 并集中管理.
  • Tomcat 通过 **反射 **这样的语法机制来创建被 @WebServlet 注解修饰的类的实例. 这些实例被创建完了之后, 会点调用其中的 init 方法进行初始化. (这个方法是 HttpServlet 自带的, 我们自己写的类可以重写 init)
  • 这些实例被销毁之前, 会调用其中的 destory 方法进行收尾工作. (这个方法是 HttpServlet 自带的, 我们自己写的类可以重写 destory)
  • Tomcat 内部也是通过 Socket API 进行网络通信.
  • Tomcat 为了能同时相应多个 HTTP 请求, 采取了多线程的方式实现. 因此 Servlet 是运行在 **多线程环境 **下的.

2) Tomcat 处理请求流程(doHttpRequest)

class Tomcat {
    
    
    void doHttpRequest(Socket socket) {
    
    
        // HTTP 服务器类似的原理,进行 HTTP 协议的请求解析,和响应构建
        HttpServletRequest req = HttpServletRequest.parse(socket);
        HttpServletRequest resp = HttpServletRequest.build(socket);
        
        // 判断 URL 对应的文件是否可以直接在我们的根路径上找到对应的文件,如果找到,就是静态
内容
        // 直接使用我们学习过的 IO 进行内容输出
        if (file.exists()) {
    
    
            // 返回静态内容
            return;
       }
        
        // 走到这里的逻辑都是动态内容了
        
        // 根据配置,按照 URL -> servlet-name -> Servlet 对象的链条
        // 最终找到要处理本次请求的 Servlet 对象
        Servlet ins = findInstance(req.getURL());
        
        // 调用 Servlet 对象的 service 方法
        // 这里就会最终调用到我们自己写的 HttpServlet 的子类里的方法了
        try {
    
    
       ins.service(req, resp); 
       } catch (Exception e) {
    
    
            // 返回 500 页面,表示服务器内部错误
       }
   }
}

步骤:

  1. 进行 HTTP 协议的请求解析,和响应构建
  2. 判断是不是静态内容(file.exists()),如果是直接找到对应的文件把文件的内容通过 Socket 返回;不是继续
  3. 找到要处理本次请求的 Servlet 对象(URL -> servlet-name -> Servlet 对象)
  4. 调用 Servlet 对象的 service 方法,最终调用到我们自己写的 HttpServlet 的子类里的方法(异常返回 500 页面,表示服务器内部错误)

**小结 **

  • Tomcat 从 Socket 中读到的 HTTP 请求是一个字符串, 然后会按照 HTTP 协议的格式解析成一个 HttpServletRequest 对象.
  • Tomcat 会根据 URL 中的 path 判定这个请求是请求一个静态资源还是动态资源. 如果是静态资源, 直接找到对应的文件把文件的内容通过 Socket 返回. 如果是动态资源, 才会执行到 Servlet 的相关逻辑.
  • Tomcat 会根据 URL 中的 Context Path 和 Servlet Path 确定要调用哪个 Servlet 实例的 service 方法.
  • 通过 service 方法, 就会进一步调用到我们之前写的 doGet 或者 doPost

3) Servlet 的 service 方法的实现

protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    
    
        String method = req.getMethod();
        long lastModified;
        if (method.equals("GET")) {
    
    
            lastModified = this.getLastModified(req);
            if (lastModified == -1L) {
    
    
                this.doGet(req, resp);
            } else {
    
    
                long ifModifiedSince = req.getDateHeader("If-Modified-Since");
                if (ifModifiedSince < lastModified) {
    
    
                    this.maybeSetLastModified(resp, lastModified);
                    this.doGet(req, resp);
                } else {
    
    
                    resp.setStatus(304);
                }
            }
        } else if (method.equals("HEAD")) {
    
    
            lastModified = this.getLastModified(req);
            this.maybeSetLastModified(resp, lastModified);
            this.doHead(req, resp);
        } else if (method.equals("POST")) {
    
    
            this.doPost(req, resp);
        } else if (method.equals("PUT")) {
    
    
            this.doPut(req, resp);
        } else if (method.equals("DELETE")) {
    
    
            this.doDelete(req, resp);
        } else if (method.equals("OPTIONS")) {
    
    
            this.doOptions(req, resp);
        } else if (method.equals("TRACE")) {
    
    
            this.doTrace(req, resp);
        } else {
    
    
            String errMsg = lStrings.getString("http.method_not_implemented");
            Object[] errArgs = new Object[]{
    
    method};
            errMsg = MessageFormat.format(errMsg, errArgs);
            resp.sendError(501, errMsg);
        }

    }
  • Servlet 的 service 方法内部会根据当前请求的方法, 决定调用其中的某个 doXXX 方法.
  • 在调用 doXXX 方法的时候, 就会触发 **多态 **机制, 从而执行到我们自己写的子类中的 doXXX 方法.

猜你喜欢

转载自blog.csdn.net/qq_53869058/article/details/132430641