Servlet學習筆記 - GenericServlet、HttpServlet

1、前言

  在《Servlet工作原理和過程》中,我們學習到了:Servlet接口是Servlet技術的核心,所有的Servlet類都必須直接或者間接實現Servlet接口。Servlet接口定義了Servlet類與Servlet容器之間的契約。因爲Servlet是一個接口的原因,所以如果編寫一個Servlet類,就需要把該接口下的所有方法實現了,同時還需要維護一個ServletConfig對象,這樣是非常麻煩。所以,Servlet api中提供了一個抽象類對Servlet接口進行了簡單實現,下面我們就來學習這個抽象類。

2、Servlet接口的類層級結構

在這裏插入圖片描述
  如上圖所示,GenericServlet實現了Servlet接口,然後HttpServlet又繼承了GenericServlet抽象類。在實際的Web應用開發中,其實一般都是直接繼承HttpServlet來實現自己的Servlet類的。

3、Servlet類的作用

  在前面的學習中已經知道了Servlet技術主要是用來實現Web應用的。這個Servlet類就是用來接收請求,處理請求,最後返回響應結果的。每一個Servlet類就對應了一類請求的處理方式。

4、GenericServlet抽象類

  GenericServlet類是一個與協議無關的通用的Servlet實現。如果編寫Servlet類是應用在Web應用中,可以直接實現HttpServlet抽象類。GenericServlet類實現了Servlet和ServletConfig兩個接口,其中Servle約束了Servlet程序與容器間調用的規約,ServletConfig接口中定義了初始化Servlet程序需要的信息和方法。

GenericServlet類的定義如下:

public abstract class GenericServlet implements Servlet, ServletConfig, java.io.Serializable {

	//省略了其他內容……

}

1. 變量 ServletConfig 實例

private transient ServletConfig config;

  在GenericServlet類中定義一個ServletConfig的變量,當通過init()方法進行初始化的時候,就會把由Servlet容器創建來的ServletConfig實例記錄到該變量中。而在GenericServlet類中關於ServletConfig接口的實現方法中,都是通過這個ServletConfig實例去實現的。需要注意的是:該變量用了transient關鍵字,表示該成員變量不參與序列化過程。

2. init()方法
  在GenericServlet類中有兩個init()方法,帶參數的init()方法是供Servlet容器來初始化Servlet類的實例,而不帶參數的init()方法是供開發者來重寫init()方法中的邏輯,因爲這樣開發者就不需要調用super.init(config);了。

 public void init(ServletConfig config) throws ServletException {
	this.config = config;
	this.init();
 }
public void init() throws ServletException {

}

3.針對ServletConfig接口中的方法實現
在GenericServlet類中,針對ServletConfig接口中方法實現方式是一樣:首先通過getServletConfig()方法獲取到保存的ServletConfig實例,如果ServletConfig實例爲null,就拋出異常,否則就調用ServletConfig實例中對應的方法。示例如下:

public ServletContext getServletContext() {
        ServletConfig sc = getServletConfig();
        if (sc == null) {
            throw new IllegalStateException(
                lStrings.getString("err.servlet_config_not_initialized"));
        }

        return sc.getServletContext();
    }

4.service()方法實現
該方法還是抽象方法,留給了子類去實現。在HttpServlet中,該方法被實現了。

 public abstract void service(ServletRequest req, ServletResponse res) throws ServletException, IOException;

5.log()方法實現
該方法是GenericServlet類新增的方法,實際上,還是通過getServletContext()方法獲取ServletConfig中存儲的ServletContext實例對象,然後再通過ServletContext實例對象的log()方法實現。

public void log(String msg) {
	getServletContext().log(getServletName() + ": "+ msg);
}

public void log(String message, Throwable t) {
	getServletContext().log(getServletName() + ": " + message, t);
}
5、HttpServlet抽象類

  HttpServlet類是抽象類GenericServlet進一步的實現,用於HTTP協議的Servlet程序,主要用來實現Web應用。在HttpServlet的子類中,一般至少需要實現下面的一個方法:

  • doGet 處理request的get請求
  • doPost 處理request的post請求
  • doPut 處理request的put請求
  • doDelete 處理request的doDelete請求
  • init、destroy 管理servlet生命週期中的相關資源
  • getServletInfo 獲取當前servlet的相關信息

  在HttpServlet的子類中,一般不需要在重寫service、doTrace、doOptions等方法,因爲這些方法中已經通過解析Http的請求,根據請求的具體類型,分發到具體的doXXX方法中進行處理,所以實際中只需重寫這些doXXX方法即可。
  在Servlet程序中,一般都是在多線程環境中使用的,而Servlet程序中訪問的公共資源,就需要考慮如何同步訪問這些資源。

5.1、service()方法
  在HttpServlet類的service()方法中,主要做了兩件事:一是判斷傳進來的參數是否是HttpServletRequest、HttpServletResponse類型;二是根據請求類型,轉發到對應的doXXX方法進行處理。HttpServlet的實現子類,一般不需要覆蓋該方法。具體代碼如下:

 @Override
    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
        HttpServletRequest  request;
        HttpServletResponse response;
        
        if (!(req instanceof HttpServletRequest &&
                res instanceof HttpServletResponse)) {
            throw new ServletException("non-HTTP request or response");
        }
        request = (HttpServletRequest) req;
        response = (HttpServletResponse) res;

        service(request, response);
    }
}
protected void service(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException
    {
        String method = req.getMethod();

        if (method.equals(METHOD_GET)) {
            long lastModified = getLastModified(req);
            if (lastModified == -1) {
                doGet(req, resp);
            } else {
                long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
                if (ifModifiedSince < lastModified) {
                    maybeSetLastModified(resp, lastModified);
                    doGet(req, resp);
                } else {
                    resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
                }
            }
        } else if (method.equals(METHOD_HEAD)) {
            long lastModified = getLastModified(req);
            maybeSetLastModified(resp, lastModified);
            doHead(req, resp);

        } else if (method.equals(METHOD_POST)) {
            doPost(req, resp);
            
        } else if (method.equals(METHOD_PUT)) {
            doPut(req, resp);
            
        } else if (method.equals(METHOD_DELETE)) {
            doDelete(req, resp);
            
        } else if (method.equals(METHOD_OPTIONS)) {
            doOptions(req,resp);
            
        } else if (method.equals(METHOD_TRACE)) {
            doTrace(req,resp); 
        } else {
            String errMsg = lStrings.getString("http.method_not_implemented");
            Object[] errArgs = new Object[1];
            errArgs[0] = method;
            errMsg = MessageFormat.format(errMsg, errArgs);
            resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
        }
    }

5.2、doXXX()方法
  doGet()和doPost()、doPut()、doDelete()等方法,實現方式基本一樣,這裏不以doGet()方法爲例來進行分析。
  doGet()方法,主要用來處理request的get、head請求,其中head請求本質上就是一個沒有響應體的get請求。需要注意的是:實際上doGet()方法沒有有效的邏輯,所以在真正使用的時候,還是需要根據需求進行實現具體邏輯的。在doHead()方法中,是通過NoBodyResponse、NoBodyOutputStream類實現了沒有響應體的head請求,後續學習HttpServletResponse的時候,在具體分析。

protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException{

        String protocol = req.getProtocol();
        String msg = lStrings.getString("http.method_get_not_supported");
        if (protocol.endsWith("1.1")) {
            resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);
        } else {
            resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
        }
    }
 protected void doHead(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException
    {
        NoBodyResponse response = new NoBodyResponse(resp);
        
        doGet(req, response);
        response.setContentLength();
    }

5.3、doOptions()方法
  doOptions()方法主要用來返回當前Servlet應用支持的request請求方式,比如:“Allow: GET, HEAD, TRACE, OPTIONS”。該方法一般也不需要進行重寫。
  在該方法的實現中,首先通過getAllDeclaredMethods()方法獲取當前Servlet程序實例中的可用方法,然後再循環驗證是否包含get、head、post、put、delete、trace、option方法,其中get和head是成對存在的,即get可用,namehead就可用,而trace、option默認就是可用的。
  代碼比較簡單,不再貼出。

5.4、doTrace()方法
  該方法主要是爲了支持與客戶端進行調試而存在的。該方法一般也不需要被重寫。

protected void doTrace(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        
        int responseLength;
        String CRLF = "\r\n";
        StringBuilder buffer = new StringBuilder("TRACE ").append(req.getRequestURI())
            .append(" ").append(req.getProtocol());
            
        Enumeration<String> reqHeaderEnum = req.getHeaderNames();

        while( reqHeaderEnum.hasMoreElements() ) {
            String headerName = reqHeaderEnum.nextElement();
            buffer.append(CRLF).append(headerName).append(": ")
                .append(req.getHeader(headerName));
        }

        buffer.append(CRLF);

        responseLength = buffer.length();

        resp.setContentType("message/http");
        resp.setContentLength(responseLength);
        ServletOutputStream out = resp.getOutputStream();
        out.print(buffer.toString());
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章