SpringMVC源碼解析之GenericServlet和HttpServlet

SpringMVC源碼解析之Servlet

一、GenericServlet

從類名上就能看出,GenericServlet是一個一般性的,與協議無關的Servlet類。
GenericServlet作爲Servlet接口的默認實現,主要實現了下面功能

1. 實現了ServletConfig接口

GenericServlet實現了ServletConfig接口,可以通過Genericervlet直接調用ServletConfig的方法。
當然,雖然可以通過Genericervlet直接調用ServletConfig的方法,但實際上還是先獲取到ServletConfig對象在調用其方法實現的,如getInitParameter方法,其它ServletConfig方法也是如此。

public String getInitParameter(String name) {
   return getServletConfig().getInitParameter(name);
}

2. 提供生命週期方法的默認實現

Servlet有兩個生命週期方法,分別是初始化Servlet#init(ServletConfig)和銷燬方法Servlet#destroy()。

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

對於init方法,提供了無參的init方法,默認不進行處理。GenericServlet的子類可以直接覆蓋無參的init的方法以自定義初始化邏輯。
(1)定義了成員變量config,將參數賦給config。方便了ServletConfig接口的方法可以通過config對象來實現
(2)子類對於init邏輯的自定義時可以不用關注config對象的處理,只需要覆蓋無參的init方法。如果對有參的init方法進行了覆蓋,那麼無參init方法將會失效,而且需要調用super.init(config),否則與config對象相關的方法都需要覆蓋重寫。

public void destroy() {
}

對於destroy方法,默認也是不進行處理。

3. log功能

GenericServlet提供了兩個日誌方法,分別爲記錄日誌和記錄異常。

//記錄日誌
public void log(String msg) {
    getServletContext().log(getServletName() + ": " + msg);
}
//記錄異常
public void log(String message, Throwable t) {
    getServletContext().log(getServletName() + ": " + message, t);
}

二、HttpServlet

HttpServlet是針對HTTP協議的Servlet實現類,繼承了GenericServlet類。
HttpServlet類最主要的作用是重寫了service方法,定義了對HTTP請求的處理方法。

public void service(ServletRequest req, ServletResponse res)
        throws ServletException, IOException {
    HttpServletRequest request;
    HttpServletResponse response;

	//將ServletRequest和ServletResponse轉化成HttpServletRequest和HttpServletResponse進行處理
    try {
        request = (HttpServletRequest) req;
        response = (HttpServletResponse) res;
    } catch (ClassCastException e) {
        throw new ServletException("non-HTTP request or response");
    }
    service(request, response);
}
//根據HTTP請求類型進行不同的處理
protected void service(HttpServletRequest req, HttpServletResponse resp)
		throws ServletException, IOException {
	
	String method = req.getMethod();

	//GET請求
	if (method.equals(METHOD_GET)) {
		//lastModified服務器端資源的修改時間
		long lastModified = getLastModified(req);
		//不支持if-modified-since,進入doGet
		if (lastModified == -1) {
			// servlet doesn't support if-modified-since, no reason
			// to go through further expensive logic
			doGet(req, resp);
		} else {
			//If-Modified-Since,瀏覽器端緩存的修改時間
			long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
			//服務器端在瀏覽器之後發生過修改,緩存無效,進入doGet
			if (ifModifiedSince < (lastModified / 1000 * 1000)) {
				// If the servlet mod time is later, call doGet()
				// Round down to the nearest second for a proper compare
				// A ifModifiedSince of -1 will always be less
				//更新服務器端資源的修改時間
				maybeSetLastModified(resp, lastModified);
				doGet(req, resp);
			} else {
				//服務器端在瀏覽器之後沒有發生過修改,返回304,直接使用緩存
				resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
			}
		}

	} else if (method.equals(METHOD_HEAD)) {
		//HEAD請求,和GET類似,只是不需要返回響應
		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 {
		//
		// Note that this means NO servlet supports whatever
		// method was requested, anywhere on this server.
		//
		//異常,HttpServlet不支持該方式的請求
		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);
	}
}

對於GET請求,需要考慮是否可以使用緩存的情況。
除了可能使用緩存以外的場景,最終都會進入doXXX方式進入處理。
doGet,doPost, doDelete,doPut的方法都類似,只是一個例子,實際使用時在子類中需要進行覆蓋。

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

HEAD請求可以簡單理解成不需要相應的GET請求

protected void doHead(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException {
    NoBodyResponse response = new NoBodyResponse(resp);

    doGet(req, response);
    response.setContentLength();
}

OPTIONAL和TRACE都用於調試,因此HttpServlet中給出了默認實現。
TRACEQ請求是將請求內容在服務器中進行顯示。

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

    int responseLength;

    String CRLF = "\r\n";
    String responseString = "TRACE " + req.getRequestURI() +
            " " + req.getProtocol();

    Enumeration reqHeaderEnum = req.getHeaderNames();

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

    responseString += CRLF;

    responseLength = responseString.length();

    resp.setContentType("message/http");
    resp.setContentLength(responseLength);
    ServletOutputStream out = resp.getOutputStream();
    out.print(responseString);
    out.close();
    return;
}

OPTIONAL請求返回服務器支持的請求類型。

protected void doOptions(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException {
    Method[] methods = getAllDeclaredMethods(this.getClass());

    boolean ALLOW_GET = false;
    boolean ALLOW_HEAD = false;
    boolean ALLOW_POST = false;
    boolean ALLOW_PUT = false;
    boolean ALLOW_DELETE = false;
    boolean ALLOW_TRACE = true;
    boolean ALLOW_OPTIONS = true;

    for (int i = 0; i < methods.length; i++) {
        Method m = methods[i];

        if (m.getName().equals("doGet")) {
            ALLOW_GET = true;
            ALLOW_HEAD = true;
        }
        if (m.getName().equals("doPost"))
            ALLOW_POST = true;
        if (m.getName().equals("doPut"))
            ALLOW_PUT = true;
        if (m.getName().equals("doDelete"))
            ALLOW_DELETE = true;
    }

    String allow = null;
    if (ALLOW_GET)
        if (allow == null) allow = METHOD_GET;
    if (ALLOW_HEAD)
        if (allow == null)
            allow = METHOD_HEAD;
        else
            allow += ", " + METHOD_HEAD;
    if (ALLOW_POST)
        if (allow == null)
            allow = METHOD_POST;
        else
            allow += ", " + METHOD_POST;
    if (ALLOW_PUT)
        if (allow == null)
            allow = METHOD_PUT;
        else
            allow += ", " + METHOD_PUT;
    if (ALLOW_DELETE)
        if (allow == null)
            allow = METHOD_DELETE;
        else
            allow += ", " + METHOD_DELETE;
    if (ALLOW_TRACE)
        if (allow == null)
            allow = METHOD_TRACE;
        else
            allow += ", " + METHOD_TRACE;
    if (ALLOW_OPTIONS)
        if (allow == null)
            allow = METHOD_OPTIONS;
        else
            allow += ", " + METHOD_OPTIONS;

    resp.setHeader("Allow", allow);
}
private Method[] getAllDeclaredMethods(Class c) {
    if (c.getName().equals("javax.servlet.http.HttpServlet"))
        return null;

    int j = 0;
    Method[] parentMethods = getAllDeclaredMethods(c.getSuperclass());
    Method[] thisMethods = c.getDeclaredMethods();

    if (parentMethods != null) {
        Method[] allMethods =
                new Method[parentMethods.length + thisMethods.length];
        for (int i = 0; i < parentMethods.length; i++) {
            allMethods[i] = parentMethods[i];
            j = i;
        }
        j++;
        for (int i = j; i < thisMethods.length + j; i++) {
            allMethods[i] = thisMethods[i - j];
        }
        return allMethods;
    }
    return thisMethods;
}

可以看到,OPTIONAL和TRACE請求是默認支持的,對於其他類型的請求則根據HttpServlet中的相應doXXX方法是否是覆蓋重寫來進行判斷。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章