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