從頭開始實現一個小型spring框架——手寫Spring之集成Tomcat服務器

寫在前面

最近學習了一下spring的相關內容,所以也就想要照貓畫虎地記錄和實現一下spring的框架,通過閱讀這些也希望能夠消除對Spring框架的恐懼,其實細心閱讀框架也很容易理解。

這是手寫Spring系列的第二篇文章,本篇文章將會對web容器對http請求的處理流程進行簡答的介紹,然後具體介紹在mini-spring框架中集成tomcat容器的步驟,一步步完善我們的mini-spring框架。
在閱讀這篇文章之前,希望你已經看過了上一篇我們對starter啓動器的實現,然後繼續集成tomcat到我們的mini-spring中。

項目的源碼我放在了github上:源碼地址

我會在這裏整理文章的系列目錄:

  1. 從頭開始實現一個小型spring框架——手寫Spring之實現SpringBoot啓動
  2. 從頭開始實現一個小型spring框架——手寫Spring之集成Tomcat服務器
  3. 從頭開始實現一個小型spring框架——控制器controller的實現
  4. 從頭開始實現一個小型spring框架——實現Bean管理(IOC與DI)

一、Web服務模型及servlet

在正式實現之前我們需要了解tomcat服務器具體都做了哪些事情,瞭解web容器對數據的處理流程,才能夠更好地吃透我們的min-spring框架。

1.1 Web服務器

  • 監聽一個TCP端口,如tomcat默認監聽的8080端口,瀏覽器默認的80端口。
    根據操作系統和計算機網絡的相關知識,在傳輸層實現端到端的通信需要通過端口,每個端口又被應着的應用程序監聽着,當有消息通過網絡一層層尋址,找到相應ip下相應的端口,便把數據放到相應的端口,進而由應用程序解析相應的內容。
  • 轉發請求,回覆相應
  • 本身沒有業務邏輯,僅負責連接操作系統和應用程序代碼

1.2 請求流程

客戶端從瀏覽器發出請求,通過網絡將數據bit流傳輸至Web服務器(細節方面涉及計算機網絡的分層模型和數據傳輸過程,這裏不過多介紹),其中還包含了數據請求的端口以及定義的各種協議信息,網卡拿到網絡中傳來的bit流,將數據轉換爲字節流,並交給相應的端口處理(本篇採用的是8899端口)。而我們的web容器如tomcat,就在監聽着這些端口,並將傳來的請求移交給相應的代碼處理,隨時處理網絡中傳來的數據。但具體處理方式對於web容器來說是一個黑盒容器並不關係具體的處理過程是怎樣的,他只負責接收請求和發回相應兩件事情。

二、實現

要在mini-spring項目中集成我們的tomcat,首先需要在gradle中添加相應的依賴。

framework的build.gradle中的dependences添加tomcat的embed版本的相關依賴(因爲只是簡單實現,所以選擇這個版本)
包結構(web包下增加server包和servlet包)

我們需要在framework的依賴中添加這麼一段:


    // https://mvnrepository.com/artifact/org.apache.tomcat.embed/tomcat-embed-core
    compile group: 'org.apache.tomcat.embed', name: 'tomcat-embed-core', version: '8.5.23'
    

整體的代碼

plugins {
    id 'java'
}

group 'com.qcby'
version '1.0-SNAPSHOT'

sourceCompatibility = 1.8

repositories {
    mavenCentral()
}

dependencies {
    testCompile group: 'junit', name: 'junit', version: '4.12'
    // https://mvnrepository.com/artifact/org.apache.tomcat.embed/tomcat-embed-core
    compile group: 'org.apache.tomcat.embed', name: 'tomcat-embed-core', version: '8.5.23'
}

在web包下新建一個server包用於保存容器相關的代碼

新建TomcatServer類用於實現我們的Tomcat

package com.qcby.web.server;

import com.qcby.web.servlet.TestServlet;
import org.apache.catalina.Context;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.startup.Tomcat;

/**
 * @author kevinlyz
 * @ClassName TomcatServer
 * @Description 集成Tomcat服務器
 * @Date 2019-06-05 13:10
 **/
public class TomcatServer {
    private Tomcat tomcat;
    private String[] agrs;

    public TomcatServer(String[] agrs) {
        this.agrs = agrs;
    }

    public void startServer() throws LifecycleException {
        //實例化tomcat
        tomcat = new Tomcat();
        tomcat.setPort(8899);
        tomcat.start();
        //實例化context容器
        Context context = new StandardContext();
        context.setPath("");
        context.addLifecycleListener(new Tomcat.FixContextListener());
        TestServlet testServlet = new TestServlet();
        Tomcat.addServlet(context,"testServlet",testServlet).setAsyncSupported(true);
        
        //添加URL映射
        context.addServletMappingDecoded("/test.json","testServlet");
        tomcat.getHost().addChild(context);

        //設置守護線程防止tomcat中途退出
        Thread awaitThread = new Thread("tomcat_await_thread."){
            @Override
            public void run() {
                TomcatServer.this.tomcat.getServer().await();
            }
        };
        //設置爲非守護線程
        awaitThread.setDaemon(false);
        awaitThread.start();
    }
}

servlet包下新建TestServlet用於響應請求

package com.qcby.web.servlet;

import javax.servlet.*;
import java.io.IOException;

/**
 * @author kevinlyz
 * @ClassName TestServlet
 * @Description 
 * @Date 2019-06-05 13:28
 **/
public class TestServlet implements Servlet {
    @Override
    public void init(ServletConfig config) throws ServletException {

    }

    @Override
    public ServletConfig getServletConfig() {
        return null;
    }

    @Override
    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
        res.getWriter().write("test");
    }

    @Override
    public String getServletInfo() {
        return null;
    }

    @Override
    public void destroy() {

    }
}

最後在MiniApplication中實例化我們的Tomcat容器

package com.qcby.starter;

import com.qcby.web.server.TomcatServer;
import org.apache.catalina.LifecycleException;

/**
 * @author kevinlyz
 * @ClassName MiniApplication
 * @Description 框架的入口類
 * @Date 2019-06-04 19:21
 **/
public class MiniApplication {
    public static void run(Class<?> cls,String[] args){
        System.out.println("Hello mini-spring application!");
        TomcatServer tomcatServer = new TomcatServer(args);
        try {
            tomcatServer.startServer();
        } catch (LifecycleException e) {
            e.printStackTrace();
        }
    }
}

然後gradle build

輸入命令:java -jar test/build/libs/test-1.0-SNAPSHOT.jar
看到控制檯打印8899端口的啓動

打開瀏覽器

看到response返回的test字符串!

喜大普奔,大功告成!

三、小結

這是手寫Spring系列的第二篇文章,本篇文章在第一篇實現SpringBoot啓動的基礎上進一步將Tomcat容器集成在了我們的mini-spring框架中。首先對容器處理請求的邏輯進行了簡單介紹,分析了tomcat容器對請求的處理和響應,對於內部數據究竟是怎樣處理的,web容器並不關心。然後對tomcat的集成做了具體實現,我們新添加了TomcatServer和TestServlet類分別實例化我們的容器和測試我們的Servlet請求攔截,當TestServlet正確匹配到我們請求的/test.json,併成功返回一個test字符串,說明我們的Servelt是有效的。

P.S. 在TomcatServer實例化容器的時候,我們設置了一個守護線程,以防止容器的中途退出

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章