《看透Spring MVC 源代碼分析與實踐》讀書筆記——詳解Servlet

  最近在看《看透Spring MVC 源代碼分析與實踐》這本書,以爲真心不錯。這本書不僅僅講的是Spring MVC,第一章爲網站基礎知識,包括網站架構、常見協議、Servlet和Tomcat等內容。雖然在作開發的時候並不會直接用到,不過理解了以後可讓咱們在進行具體開發的時候更加駕輕就熟,還能夠經過對具體內容的學習學到一些優秀思想。此次我想先從最簡單的Servlet內容談起,畢竟每一個Java Web開發人員應該對Servlet都不陌生。文章內容主要是對書中知識點的概括總結,外加一部分本身的理解。
  你們都知道Servlet是Server+Applet的縮寫,表示一個服務器應用。其實Servlet就是一套規範,按照這套規範寫的代碼就能夠直接在Java的服務器上運行。Servlet 3.1中Servlet的結構以下圖所示(來源:www.54tianzhisheng.cn/2017/07/09/… ,你們能夠沒事看看這個博客,有些內容講的很好): java

圖片未加載成功
  Servlet 3.1中Servlet接口的定義以下:

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

  init方法在容器啓動時被容器調用且只會調用一次(當load-on-startup設置爲負數或不設置時會在Servlet第一次被用到時被調用)。init方法被調用時,由容器傳給它一個ServletConfig類型的參數,ServletConfig顧名思義指的是Servlet的配置,咱們在web.xml中定義Servlet時經過init-param標籤配置的參數就是經過ServletConfig來保存的;
  getServletConfig方法用於獲取這個ServletConfig對象;
  service方法用於具體處理一個請求,它處理的請求是不限請求類型的。具體怎麼處理呢,一會講到實現它的方法的時候會說;
  getServlet方法能夠獲取一些Servlet相關的信息,須要本身實現,默認返回空字符串;
  destroy方法主要用於在Servlet銷燬(通常指關閉服務器)時釋放一些資源,也只會調用一次。web

ServletConfig

  ServletConfig接口定義以下:服務器

public interface ServletConfig {
  
    public String getServletName();
  
    public ServletContext getServletContext();
  
    public String getInitParameter(String name);
  
    public Enumeration<String> getInitParameterNames();
}
複製代碼

  getServletName用於獲取Servlet的名字,也就是咱們在web.xml中定義的servlet-name;
  getInintParameter用於獲取init-param配置的參數;
  getInitParameterNames用於獲取配置的全部init-param的名字集合;
  getServletContext的返回值ServletContext表明這個應用自己。 我身邊不少人都不明白「表明應用自己」是什麼意思,這是說ServletContext裏設置的參數,能夠被當前應用的全部Servlet共享。你們常常在Session或Application中保存參數,然後者不少時候就是保存在了ServletContext中。能夠把ServletConfig理解成Servlet級的,而ServletContext是Context(也就是Application)級的。固然,ServletContext的功能要強大不少,並非保存一下配置參數。
  可能上面說的比較抽象,下面來舉一個例子。在web.xml文件中寫以下配置信息:架構

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>application-context.xml</param-value>
</context-param>
<servlet>
    <servlet-name>DemoServlet</servlet-name>
    <servlet-class>com.hawk.DemoServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>demo-servlet.xml</param-value>
    </init-param>
</servlet>
複製代碼

  上面經過context配置的contextConfigLocation配置到了ServletContext中,而經過servlet下的init-param配置的contextConfigLocation配置到了ServletConfig中。在Servlet中能夠分別經過它們的getInitParameter方法進行獲取:app

String contextLocation = getServletConfig().getServletContext().getInitParameter("contextConfigLocation");
String servletLocation = getServletConfig().getInitParameter("contextConfigLocation");
複製代碼

  爲了操做方便,GenericServlet定義了getInitParameter方法,內部返回getServletConfig().getInitParameter的返回值,所以若是須要獲取SevletConfig中的參數,能夠直接調用getInitParameter,而沒必要再調用getServletConfig()。
  ServletContext中常見的用法就是保存Application級的屬性,經過調用setAttribute方法:框架

getServletContext().setAttribute("contextConfigLocation", "new path");
複製代碼

  而ServletConfig不可設置屬性。學習

GenericServlet

  GenericServlet是Servlet的默認實現(注意:GenericServlet是一個抽象類,service方法並未被實現),是與具體協議無關的Servlet,主要作了三件事:(1)實現ServletConfig接口,能夠直接調用ServletConfig裏的方法;(2)提供了無參的init方法;(3)提供了log方法。下面分別來解釋一下:
  GenericServlet實現了ServletConfig接口,在須要調用ServletConfig中方法的時候能夠直接調用,而沒必要先獲取ServletConfig。好比,獲取ServletContext的時候能夠直接調用getServletContext,而無需調用getServletConfig().getServletContext(),不過和剛纔說的getInitParameter同樣,其底層實現實際上是在內部調用了:網站

public ServletContext getServletContext() {
    return this.getServletConfig().getServletContext();
}
複製代碼

  GenericServlet實現了Servlet的init(ServletConfig config)方法,在裏面將config設置給了內部屬性config,而後調用了無參的init方法(這個無參的init方法是GenericServlet新增的,專門用於被它的子類覆蓋):this

public void init(ServletConfig config) throws ServletException {
    this.config = config;
    this.init();
}
複製代碼

  這麼寫的做用很明顯:首先將參數config設置給了內部屬性config,這樣就能夠在ServletConfig的接口方法中直接調用config的相應方法來執行;其次,以後咱們在寫Servlet的時候就能夠只處理本身的初始化邏輯(即只需覆蓋無參的init方法),而不須要再關心config,也不須要再調用super.init(config)了。一開始說到容器啓動時會調用init(ServletConfig config)方法,而該方法會調用無參的init方法,從而實現咱們本身的初始化邏輯。須要注意的是,若是在本身的Servlet中重寫了帶參數的init方法,必定要記着調用init(config),不然這裏的config屬性接收不到值,相應的ServletConfig接口方法就不能執行了。
  GenericServlet提供兩個log方法,一個記錄日誌,一個記錄異常,具體實現是經過傳給ServletContext的日誌實現的。通常咱們都有本身的日誌處理方式,因此這兩個log方法用得不是不少。spa

HttpServlet

  HttpServlet是用HTTP協議實現的Servlet的子類,通常咱們在寫Servlet的時候就是直接繼承這個類,Spring MVC中最重要的DispatcherServlet也是繼承的這個類。分析這個類主要關心的是如何處理請求,HttpServlet主要重寫了service方法,首先將ServletRequest和ServletResponse轉換成HttpServletRequest和HttpServletResponse,而後根據Http請求的類型不一樣將請求路由到了不一樣的處理方法,代碼以下:

public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
        HttpServletRequest request;
        HttpServletResponse response;
        // 轉換request和reponse的類型
        // 若是請求類型不相符,則拋出異常
        try {
            request = (HttpServletRequest)req;
            response = (HttpServletResponse)res;
        } catch (ClassCastException var6) {
            throw new ServletException("non-HTTP request or response");
        }

        this.service(request, response);
    }

    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);
        }

    }    
複製代碼

  而像doGet、doPost這樣的具體處理方法相信你們都很熟悉了,這裏就再也不概述,重點是HttpServlet將不一樣的請求方式路由到不一樣的處理方法這個實現思想。   最後呢,想談一下我我的對Servlet這種底層事物的見解。有的人在學了各類框架後,就對Servlet、JDBC這種底層事物很不屑,畢竟實際開發中Servlet是用不到的。但我以爲,不管多麼高級的框架,本質上都是對這些底層事物的封裝,區別在於封裝的程度不一樣,瞭解這些底層事物的實現細節,對於框架的學習能起到事半功倍的做用。   最後的最後,牆裂推薦《看透Spring MVC 源代碼分析與實踐》這本書,我以爲很OK。

相關文章
相關標籤/搜索