Tomcat深入淺出——Servlet(二)

Meteor發表於2022-07-05

一、Servlet簡介

Servlet類最終開發步驟:

  • 第一步:編寫一個Servlet類,直接繼承HttpServlet
  • 第二步:重寫doGet方法或者doPost方法,重寫哪個我說的算!
  • 第三步:將Servlet類配置到web.xml檔案當中
  • 第四步:準備前端的頁面(form表單),指定請求路徑即可

  • Servlet是一個介面,在它的下面有GenericServlet
    和HttpServlet兩個實現類

我們來了解一下GenericServlet:

  • 其中的GenericServlet實現了很多方法,只保留了一個抽象方法,就是我們經常用的service方法。

  • 這或許就是為了更簡潔一點吧,我們每次不需要在像實現Servlet介面一樣,將全部的抽象方法都實現。
  • 根據他的類圖可知,HttpServlet繼承了GenericServlet這個類,那麼它為什麼要繼承GenericServlet呢?

我們來了解一下HttpServlet:

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;
                try {
                    ifModifiedSince = req.getDateHeader("If-Modified-Since");
                } catch (IllegalArgumentException var9) {
                    ifModifiedSince = -1L;
                }

                if (ifModifiedSince < lastModified / 1000L * 1000L) {
                    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);
        }

    }
  • 這段是HttpServlet中實現的Service方法的原始碼,我們可以清楚的瞭解到,Tomcat已經把這個Service寫好了,我們只需要去實現doGet()doPost()等等這些請求的方法就可以了。
  • 因為這個Service會自己判斷,我們進行訪問的是什麼請求,然後自動找到相應的方法進行處理。

二、兩種設計模式

2.1介面卡模式

介面卡:介面卡就是一種適配中介軟體,它存在於不匹配的二者之間,用於連線二者,將不匹配變得匹配,簡單點理解就是平常所見的轉接頭,轉換器之類的存在。

public interface MyInterface {
    void m1();
    void m2();
    void m3();
    void m4();
    void m5();
    void test();
    //但是這個介面中我們常用的方法只有test(),我們在實現此介面的時候,還需要實現其他方法,很累贅。
    //所以我們就需要一個介面卡!
}
public abstract class Test implements MyInterface{
    public void m1() {
    }

    public void m2() {
    }

    public void m3() {
    }

    public void m4() {
    }

    public void m5() {
    }
    //這是一個介面卡
    //在建立一個介面的實現類,我們將常用的方法設定為抽象的方法
    //這樣我們只需要繼承該類,然後實現方法即可
    public abstract void test();
}
  • 這樣做的優點就是下次我們只需要去繼承Test類,實現我們經常使用的test()方法就可以了。
  • 使我們的減少了冗餘的程式碼量
  • 同時你也會發現這個就和GenericServlet去繼承Servlet這個介面是一樣的
  • 這就是介面卡設計模式,其實很簡單,也有利於我們更加理解抽象類的作用和介面的使用
public class Realize extends Test{
    public void test() {
        //這樣我們就不需要再去實現介面中的 其他方法了
    }
}

2.2 模板設計方法模式

模板方法模式:是一種行為設計模式, 它在超類中定義了一個演算法的框架, 允許子類在不修改結構的情況下重寫演算法的特定步驟。

  • 模板方法模式建議將演算法分解為一系列步驟, 然後將這些步驟改寫為方法, 最後在 “模板方法” 中依次呼叫這些方法。 步驟可以是 抽象的, 也可以有一些預設的實現。 為了能夠使用演算法, 客戶端需要自行提供子類並實現所有的抽象步驟。 如有必要還需重寫一些步驟 (但這一步中不包括模板方法自身)。

  • 認真的去閱讀上面這段話:你就會發現他說將 演算法進行分解,最後在模板中依次呼叫:

    • 此時的你是否想起我們剛開篇介紹的HttpServlet???
    • HttpServlet繼承了 GenericServlet類,而GenericServlet類又實現了Servlet介面;HttpServlet裡面的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;
                try {
                    ifModifiedSince = req.getDateHeader("If-Modified-Since");
                } catch (IllegalArgumentException var9) {
                    ifModifiedSince = -1L;
                }

                if (ifModifiedSince < lastModified / 1000L * 1000L) {
                    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);
        }

    }
  • 一大堆的ifelse ifelse 這不就是所謂的將演算法分塊提煉了出來嗎?然後我們只需要去刻畫doGet()doPost()等方法,程式的主框架是不變的,只是其中的細節需要我們自己去實現。
  • 我個人目前覺得和介面卡模式比較相似,但略有不同之處。

三、Servlet物件的宣告週期

3.1 Servlet物件是由誰來維護的?

  • Servlet物件的建立、以及物件上方法的呼叫、物件的銷燬這個過程,我們JavaWeb程式設計師是無權干預的。你可以仔細想想你什麼時候new出來過一個Servlet物件。
  • Servlet物件的宣告週期是由Tomcat伺服器負責的
  • Tomcat伺服器我們又可以稱之為:WEB容器
  • WEB容器來管理Servlet物件的死活

3.2 Servlet認知強化

  • 你還真可以自己new一個Servlet物件,但是並不受我們Tomcat管理,所以你自己new出來的Servlet物件,死活和Tomcat沒有關係。
  • Tomcat建立的Servlet物件,這些Servlet都會被放到一個集合當中(HashMap),只有放到HashMap集合的Servlet才能夠被Tomcat容器管理。
  • Tomcat容器底層應該有一個HashMap這樣的集合,在這個集合當中儲存了Servlet物件和請求路徑之間的關係,我想此時你更能夠想到為什麼我們總是在xml中如此定義?

  • 我們可以將Servlet物件稱之為假單例模式,因為Servlet只有一個物件呀。?
  • 配置在web.xml對應Servlet程式的標籤下新增<load-on-startup>1</load-on-startup>配置,指定改Servlet物件在Web應用啟動時建立

3.3 關於Servlet中的方法

  • 通過Servlet中的方法,我們就可以瞭解到它的生命週期、執行機制。
  • 無引數構造方法、init方法只在第一次使用者傳送請求的時候執行

  • 只要使用者傳送一次請求,service方法必然會被Tomcat呼叫一次。

  • 關於Servlet類中方法的呼叫次數:

    • 構造方法只執行一次
    • init方法只執行一次
    • service方法:使用者傳送n次請求執行n次
    • destroy方法:只執行一次

什麼時候使用destroy方法:

  • 通常在destroy方法當中,進行資源的關閉,馬上物件要被銷燬了,還有什麼沒有關閉的,抓緊時間關閉資源。

四、Servlet對映問題

  • 只要是前端傳送的請求,就必須要寫專案名!
  • 當我們進行轉發的時候需要加上"/":request.getRequestDispatcher('/b');
  • 當我們在web.xml中配置路徑時,需要加上<url-pattern>/helloServlet</url-pattern>

url-pattern匹配規則:

  • "/":弱的 全路徑匹配
  • ".do":以特殊方式結尾的匹配
  • "/*":強的 全路徑匹配
  • "/hello/*":有字首的 所以匹配

五、HttpServletRequest

  • 首先HttpServletRequest這個介面實現了ServletRequest介面,那麼他們兩個有什麼區別呢?
    • HttpServletRequest:這個名字帶有http,所以它的裡面就有一些關於對瀏覽器的處理方法;例如

5.1 GET請求引數

● 在瀏覽器的url裡傳送,以查詢字串方式拼接引數(?key=value&key1=value1)
● 使用Ajax,設定請求型別為GET,在請求url後拼接查詢字串
● 用form表單傳送

    /**
     * GET,傳送的請求引數在請求行內,
     * 請求行的資訊會由Tomcat自動解碼 (utf-8)
     */
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("HTTPServletOne doGet");
        // 1 獲取請求引數
        System.out.println("根據getParameter獲取請求引數值 :"+req.getParameter("name"));
        // 2獲取所有引數名
        Enumeration<String> paras = req.getParameterNames();
        String s = null;
        while (paras.hasMoreElements()) {
            s = paras.nextElement();
            System.out.println("根據getParameterNames得到的引數名:"+s +"引數值:"+ req.getParameter(s));
        }
    }

5.2 POST請求引數

● form表單傳輸,指定請求方式為POST,預設Content-Type為application/x-www-form-urlencoded

    /**
     * 傳送POST請求,請求引數都在請求體裡
     * 請求體的內容由Servlet進行解析,預設編碼規範為ISO
     * 必須指定請求頭 Content-Type
     */
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("HTTPServletOne doPost");
        // 0 設定請求處理字符集
        req.setCharacterEncoding("utf-8");
        // 1.獲取請求引數
        System.out.println("根據getParameter獲取請求引數值 :"+req.getParameter("name"));
    }

● Ajax傳輸,指定請求方式為POST,必須新增Content-Type為application/x-www-form-urlencoded;charset=utf-8

    /**
     * 傳送POST請求,請求引數都在請求體裡
     * 請求體的內容由Servlet進行解析,預設編碼規範為ISO
     */
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("HTTPServletOne doPost");
        // 0 設定請求處理字符集
        req.setCharacterEncoding("utf-8");
        // 1 獲取JSON資料
        // 2 reader輸入流, 是從請求體開始讀的
        BufferedReader reader = req.getReader();
        String s = null;
        StringBuilder sb = new StringBuilder();
        while ((s = reader.readLine()) != null) {
            sb.append(s);
        }
        String json = sb.toString();
        // 3 通過FastJson 工具進行解析
        Person person = JSON.parseObject(json, Person.class);
        System.out.println(person);
    }

● 附上前端程式碼

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script type="text/javascript">
        function onGet() {
            //ajax
            var xhr = new XMLHttpRequest();
            xhr.open("GET","http://localhost:8080/httpServlet01?name=123&age=12");
            xhr.send();
        }
        function onPost() {
            //ajax
            //用GET方式傳送的請求引數在請求行內,請求行內的資訊會由Tomcat自動解碼(utf-8)
            //如果用POST請求,所有引數都放在了請求體裡,
            //但是請求體的內容不是Tomcat解析的, 是由Servlet進行解析的,預設的編碼規範為ISO
            //則需要宣告請求頭格式:application/x-www-form-urlencoded
            //表單傳送的話 會預設加上這個型別
            var xhr = new XMLHttpRequest();
            xhr.open("POST","http://localhost:8080/httpServlet01");
            xhr.setRequestHeader("Content-Type","application/json;charset=utf-8")
            xhr.send("{'name':'123'}");
        }
        function onPut() {
            //ajax
            var xhr = new XMLHttpRequest();
            xhr.open("PUT","http://localhost:8080/httpServlet01");
            xhr.send();
        }
        function onDelete() {
            //ajax
            var xhr = new XMLHttpRequest();
            xhr.open("DELETE","http://localhost:8080/httpServlet01");
            xhr.send();
        }
    </script>
</head>
<body>
    <h1>Hello,Servlet!</h1>
    <button onclick="onGet()">傳送GET請求</button>
    <button onclick="onPost()">傳送POST請求</button>
    <button onclick="onPut()">傳送PUT請求</button>
    <button onclick="onDelete()">傳送DELETE請求</button>
</body>
</html>

六、結尾

  • 對於Tomcat的Servlet內容就總結這麼多,若想深入學習等待後續更新。
  • 我將會繼續更新關於Java方向的學習知識,感興趣的小夥伴可以關注一下。
  • 文章寫得比較走心,用了很長時間,絕對不是copy過來的!
  • 尊重每一位學習知識的人,同時也尊重每一位分享知識的人。
  • ?你的點贊與關注,是我努力前行的無限動力。?

相關文章