為什麼要有 Servlet ,什麼是 Servlet 容器,什麼是 Web 容器?

yes的練級攻略發表於2020-12-10

本文已收錄至 https://github.com/yessimida/yes ,這裡有我的所有文章分類彙總,歡迎 star!

以下程式碼相信大家都很熟悉,大學時學 Java Web 都寫過這樣的程式碼。

從第一次接觸 Servlet 到之後的很長一段時間內,我都沒理解 Servlet 是個什麼玩意?

為什麼要有 Servlet ?

為什麼要有 Servlet 容器?

啥又是 Web 容器、HTTP 伺服器?

今兒我們們就來盤盤,並且從中來看看架構和框架的設計套路。

看完之後可能對介面、抽象會有進一步的認識。

來,上車!

正文

首先瀏覽器發起 HTTP 請求,像早期的時候只會請求一些靜態資源,這時候需要一個伺服器來處理 HTTP 請求,並且將相應的靜態資源返回。

這個伺服器叫 HTTP 伺服器。

簡單點說就是解析請求,然後得知需要伺服器上面哪個資料夾下哪個名字的靜態檔案,找到返回即可。

而隨著網際網路的發展,互動越發得重要,單純的靜態檔案滿足不了需求。

業務變得複雜,需要我們編寫程式碼來處理諸多業務。

需要根據 HTTP 請求呼叫不同的業務邏輯來響應,但是我們的業務程式碼不能跟 HTTP 伺服器耦合起來。

總不能在 HTTP 伺服器的具體實現裡面來做判斷到底需要呼叫哪個業務類吧?

這就把非業務和業務強相關了。

所以需要做一層抽象,將 HTTP 的解析和具體的業務隔離。

本質上的需求就是根據 HTTP 請求找到對應的業務實現類然後執行邏輯再返回。

業務千千萬,所以需要規定一個介面,所以業務類都實現這個介面這樣才好對接。

這就是介面的含義,就像 USB。

這個介面就是 Servlet,當然這是最狹義的解釋。

Servlet 其實是 Server Applet,全稱 Java Servlet,指的是用Java 編寫的服務端程式。

其實指代的是實現 Servlet 介面的那些業務類。

這就是 Servlet 的由來。

而 Servlet 容器其實就是管理和載入這些 Servlet 類的,拿到 HTTP 請求之後找到對應的 Servlet 類這就是 Servlet 容器要做的事情。

看到這是不是覺得還能再抽一層?因為這好像也和具體的業務實現沒關係?

是的,還能抽一層。

沒必要把 Servlet 容器做的事情和具體的業務耦合起來,業務反正照著 Servlet 介面實現就行,這樣 Servlet 容器就可以載入它和管理它。

把請求和哪個 Servlet 對應關係也抽象出來,就是 web.xml 了,我們們在配置裡面告訴 Servlet 容器對應關係即可。

我圖中的業務實現其實對應的就是我們平常的 war 包,這就是業務和 Servlet 容器的解耦。

想必你也聽過 Servlet 規範,其實 Servlet 介面和 Servlet 容器這一整套包括目錄命名啊啥的合起來就叫 Servlet 規範。

所有相關的中介軟體按照 Servlet 規範實現,我們也按 Servlet 規範來實現業務程式碼,這樣我們就能在不同場景選擇不同的 Web 中介軟體。

反正規範的目的就是為了對接方便,減少對接成本。

至此 HTTP 伺服器、Servlet 、Servlet 容器想必都清晰了。

而 Web 容器其實就是 HTTP 伺服器 + Servlet 容器,因為單單 Servlet 容器沒有解析 HTTP 請求、通訊等相關功能。

所以把 Tomcat、Jetty 等實現包含了 HTTP 伺服器和 Servlet 容器的功能,稱之為 Web 容器。

從我們的分析一層一層的剝離,一層一層的抽象,相信你對 Web 有了更進一步的認識,我再畫個 Tomcat 的分析圖,應該就很清晰了。

從上面的一步步分析可以看出:其實架構的設計就是一系列相關的抽象。

先是抽象出 HTTP 服務,用來通訊和解析協議。

再因為業務的複雜,為了不和 HTTP 服務耦合又抽象了一層 Servlet。

由 Servlet 載入和管理 Servlet ,來控制請求轉發到指定的 Servlet 實現類。

然後我們安心的開發業務即可。

因為抽象所以靈活易擴充套件,比如現在是 HTTP1.1 服務,可以換成 HTTP 2。

現在用 Tomcat 來作為 Servlet 容器,也可以換成 Jetty。

現在用原生的實現 Servlet 來做業務,也可以換成 SpringMVC。

隨意變更,因為都抽象出來了,就很好替換,只要遵循約定的介面實現即可。

框架設計的一個套路

看完了架構設計的套路,再說說框架套路。

介面和抽象類。

所有中介軟體設計必用的套路,當然我們自己的程式碼也會這樣用。

定義一個介面來約定一些動作,能做啥做啥。

然後再定義一個抽象類來實現這個介面,用來實現一些通用的邏輯,做到程式碼的複用。

然後再搞一些常用的實現類繼承抽象類,方便開發者的使用。

剩下的就留給開發者自行擴充套件即可。

然後抽象類都會使用模板方法,也就是定義執行的流程,具體實現邏輯由子類自行實現。

這就是必用的套路。

介面約束、抽象類程式碼複用、實現常用實現類方便使用、剩下的自行擴充套件。

拿 Servlet 舉例,首先定義 Servlet 介面。

public interface Servlet {
    void init(ServletConfig config) throws ServletException;
    ServletConfig getServletConfig();
    void service(ServletRequest req, ServletResponse res)throws ServletException, IOException;
    String getServletInfo();
    void destroy();
}

然後搞了個通用抽象類 GenericServlet,不過這個抽象類邏輯比較簡單。

public abstract class GenericServlet implements Servlet, ServletConfig,
        java.io.Serializable {
  ................省略一些.............
   @Override
    public ServletConfig getServletConfig() {
        return config;
    }
    @Override
    public ServletContext getServletContext() {
        return getServletConfig().getServletContext();
    }
    @Override
    public void init(ServletConfig config) throws ServletException {
        this.config = config;
        this.init();
    }
................省略一些.....................
}

然後搞了個常用的 HttpServlet 繼承了 GenericServlet。

public abstract class HttpServlet extends GenericServlet {

    private static final long serialVersionUID = 1L;

    private static final String METHOD_DELETE = "DELETE";
    private static final String METHOD_HEAD = "HEAD";
    private static final String METHOD_GET = "GET";
  ....................
}

套路就是這麼個套路,之後面試官問你介面和抽象類的問題,相信你也能答出來了。

最後

套路大家應該都 GET 到了。

想必大家都聽過“電腦科學中的每個問題都可以用一間接層解決”。

是的,基本上所有問題抽象一層都能解決。

如果一層不夠,那就兩層。

歡迎加我好友進行深入地交流,備註「進群」,拉你進交流&內推群。

平日的面試題遇到難處,或者看某個知識點翻遍全網的資料還是感覺很模糊、不透徹,可以私聊我,給我留言。

遇到合適的我會整理寫出一篇文章,我不會的去請教別人也給整出來。

那種工作遇到很細節的場景的還是別了,這種問你上司比較合適:)

歡迎關注我的公眾號【yes的練級攻略】,更多硬核文章等你來讀。

巨人的肩膀

《深入拆解Tomcat & Jetty》 李號雙


微信搜尋【yes的練級攻略】,關注 yes,回覆【123】一份20W字的演算法刷題筆記等你來領,從一點點到億點點,我們下篇見。
個人文章彙總:https://github.com/yessimida/yes 歡迎 star !

相關文章