1. 程式人生 > >淺談servlet原始碼

淺談servlet原始碼

寫在開頭:眾所周知,對於Java web專案來說,servlet是第一步,無論你使用什麼框架,都是基於servlet而進行封裝或者衍生的,所以很有必要研究一下servlet是個什麼東東。

一.servlet的架構圖

       如上圖所示,可以看出servlet是一個介面,有一個基礎的實現類,基本上所有的servlet都是基於這個介面展開的,接下來就來看看這個介面有什麼東東。

二.servlet及其相關介面和相關實現類

1.servlet及其相關介面

如上圖所示,servlet可以認為是一個web專案中的入口,首先init()初始化容器,接收一個ServletConfig引數,由容器傳入,ServletConfig就是Servlet的配置,在web.xml中定義Servlet時通過init-param標籤配置的引數由ServletConfig儲存;然後service()處理不同的請求。

接下來看ServletConfig介面

如上圖所示,ServletConfig是Servlet級別,而ServletContext是Context(也就是Application)級別,ServletContext通常利用setAttribute方法儲存Application的屬性。

然後就是ServletContext介面,首先要明確,一個web應用對應一個ServletContext,所以ServletContext的作用範圍是整個應用,明確這點很重要,這是基礎中的基礎。如下是ServletContext介面的原始碼:

public interface ServletContext {
// 返回web應用的上下文路徑,就是平時我們部署的應用的根目錄名稱,如部署在Tomcat上的webapps目錄下的demo應用,返回的就是/demo,如果部署在ROOT目錄下的話,返回空字串“”。
此外,這個路徑可以再Tomcat的server.xml裡面修改path屬性。
    String getContextPath();

// 方法入參是uriPath,是一個資源定位符的路徑。返回一個ServletContext例項,其實就是根據資源的路徑返回其servlet上下文。
    ServletContext getContext(String var1);

// 返回當前servlet容器支援的servlet規範的最高版本
    int getMajorVersion();

// 返回當前servlet容器支援的servlet規範的最低版本
    int getMinorVersion();

// 返回檔案的MIME型別,MIME型別是容器配置的,可以通過web.xml進行配置
    String getMimeType(String var1);

// 根據傳入的路徑,列出該路徑下的所有資源路徑,返回的路徑是相當於web應用的上下文根或者相對於WEB-INF/lib目錄下的各個JAR包裡面的/META-INF/resources目錄。
    Set getResourcePaths(String var1);
// 將指定路徑的資源封裝成URL例項並返回
    URL getResource(String var1) throws MalformedURLException;
// 獲取指定路徑資源的輸入流InputStream並返回
    InputStream getResourceAsStream(String var1);
// 將指定的資源包裝成RequestDispatcher例項並返回,根據資源路徑(該路徑相對於當前應用上下文根)
    RequestDispatcher getRequestDispatcher(String var1);
// 將指定的資源包裝成RequestDispatcher例項並返回,根據資源的名稱(通過伺服器控制檯或者web.xml裡面配置的,比如web.xml裡面配置servlet的名稱)。
    RequestDispatcher getNamedDispatcher(String var1);

    /** @deprecated */
    Servlet getServlet(String var1) throws ServletException;

    /** @deprecated */
    Enumeration getServlets();

    /** @deprecated */
    Enumeration getServletNames();

    void log(String var1);

    /** @deprecated */
    void log(Exception var1, String var2);
// 記錄日誌到servlet日誌檔案,servlet日誌檔案的路徑由具體的 servlet容器自己去決定。如果你是在MyEclipse、STS這類的IDE中跑應用的話,那麼日誌資訊將在控制檯(Console)輸出。如果是 釋出到Tomcat下的話,日誌檔案是Tomcat目錄下的/logs/localhost.yyyy-MM-dd.log。
    void log(String var1, Throwable var2);
// 根據資源虛擬路徑,返回實際路徑;比如getRealPath("index.jsp"),返回index.jsp在系統中的絕對路徑
    String getRealPath(String var1);

    String getServerInfo();
// 用來獲取應用的初始化引數相關資料的,根據引數名獲取引數值,引數的作用域是整個應用。這個引數是在web.xml中配置的,如<context-param>標籤的內容等等。
    String getInitParameter(String var1);
// 用來獲取應用的初始化引數相關資料的,獲取引數名集合。
    Enumeration getInitParameterNames();

    Object getAttribute(String var1);

    Enumeration getAttributeNames();

    void setAttribute(String var1, Object var2);

    void removeAttribute(String var1);

    String getServletContextName();
}

既然上面說到了RequestDispatcher這個介面,接下來也看看這個介面

public interface RequestDispatcher {
    void forward(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;

    void include(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;
}

這個介面只有兩個方法,相信大家都比較熟悉這兩個方法了。一個是轉發,一個是包含,有點類似於jsp裡面的轉發和包含。當我們要進行轉發的時候,只有使用這個方法即可,它會通過servlet容器去呼叫相關的介面實現轉發,找到對應的資原始檔,比如在上面介紹的一些相關的方法。

此外,還有ServletRequest & ServletResponse這兩個介面。對於每一個HTTP請求,servlet容器會建立一個封裝了HTTP請求的ServletRequest例項傳遞給servlet的service方法。ServletResponse則表示一個Servlet響應,其影藏了將響應發給瀏覽器的複雜性。通過ServletRequest的方法你可以獲取一些請求相關的引數,而ServletResponse則可以將設定一些返回引數資訊,並且設定返回內容。返回內容之前一般會呼叫setContentType方法設定響應的內容型別,如果沒有設定,大多數瀏覽器會預設以html的形式響應,不過為了避免出問題,我們一般都設定該項。

值得注意的是ServletResponse中定義的getWriter方法,它返回可以將文字傳給客戶端的java.io.PrintWriter。在預設的情況下,PrintWriter物件使用ISO-8859-1編碼,這有可能引起亂碼。

2.servlet相關實現

說了幾個比較常見的介面,接下來就來看看一些相關的實現類吧。第一個當然是GenericServlet,這是第一個實現了Servlet介面的類。GenericServlet是Servlet的預設實現,是與具體協議無關的。

public abstract class GenericServlet implements Servlet, ServletConfig, Serializable {
    private static final String LSTRING_FILE = "javax.servlet.LocalStrings";
    private static ResourceBundle lStrings = ResourceBundle.getBundle("javax.servlet.LocalStrings");
    private transient ServletConfig config;

    public GenericServlet() {
    }

    public void destroy() {
    }

    public String getInitParameter(String name) {
        ServletConfig sc = this.getServletConfig();
        if (sc == null) {
            throw new IllegalStateException(lStrings.getString("err.servlet_config_not_initialized"));
        } else {
            return sc.getInitParameter(name);
        }
    }

    public Enumeration getInitParameterNames() {
        ServletConfig sc = this.getServletConfig();
        if (sc == null) {
            throw new IllegalStateException(lStrings.getString("err.servlet_config_not_initialized"));
        } else {
            return sc.getInitParameterNames();
        }
    }

    public ServletConfig getServletConfig() {
        return this.config;
    }

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

    public String getServletInfo() {
        return "";
    }

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

    public void init() throws ServletException {
    }

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

    public void log(String message, Throwable t) {
        this.getServletContext().log(this.getServletName() + ": " + message, t);
    }

    public abstract void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;

    public String getServletName() {
        ServletConfig sc = this.getServletConfig();
        if (sc == null) {
            throw new IllegalStateException(lStrings.getString("err.servlet_config_not_initialized"));
        } else {
            return sc.getServletName();
        }
    }
}

實現實現了ServletConfig,可以呼叫這裡面的一些方法,比如上面說的,可以獲取servlet應用名字,獲取web.xml的一些引數值。然後還提供了有參的的init()方法,將init方法中的ServletConfig賦給一個類級變數,使得可以通過getServletConfig來獲取。同時為避免覆蓋init方法後在子類中必須呼叫super.init(servletConfig),GenericServlet還提供了一個不帶引數的init方法,當ServletConfig賦值完成就會被帶引數的init方法呼叫。這樣就可以通過覆蓋不帶引數的init方法編寫初始化程式碼,而ServletConfig例項依然得以儲存。

此外,還有兩個log方法:一個記錄日誌,一個記錄異常 。

然後就是HttpServlet,這是一個基於http協議實現的Servlet基類,也在我們平時開發過程中比較常見的一個類。

首先是定義了http協議相關的幾個方法名字變數,然後又根據這些http方法,定義了相應的處理方法,比較常見的就是doGet,doPost方法了,這些方法都基本都跟下面的程式碼差不多,只是改了一下方法名而已。

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(405, msg);
        } else {
            resp.sendError(400, msg);
        }

    }

此外,HttpServlet還重寫service方法

public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
// 對request和response進行型別強轉
        HttpServletRequest request;
        HttpServletResponse response;
        try {
            request = (HttpServletRequest)req;
            response = (HttpServletResponse)res;
        } catch (ClassCastException var6) {
            throw new ServletException("non-HTTP request or response");
        }
 //呼叫Http的請求方法處理
        this.service(request, response);
    }
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//獲取請求型別
        String method = req.getMethod();
        long lastModified;
//判斷請求型別進行不同的http方法處理,即路由
        if (method.equals("GET")) {
            lastModified = this.getLastModified(req);
            if (lastModified == -1L) {
                this.doGet(req, resp);
            } else {
                long ifModifiedSince = req.getDateHeader("If-Modified-Since");
                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);
        }

    }

 

相關連線:

http://www.cnblogs.com/nantang/p/5919323.html

https://www.cnblogs.com/fxust/p/7944242.html

https://www.jianshu.com/p/e9f31c783ff1