1. 程式人生 > >Servlet生命週期以及工作原理

Servlet生命週期以及工作原理

  最近感覺到用久了SpringMVC、Struts2等框架,反而對它們的底層實現,即Servlet,的相關知識有了許多遺忘。現在參考了網上的一些部落格,來進行一次知識點總結。
  

Servlet響應客戶端請求的過程

image_1b205lk8c1f881cu01s401jn01b6n9.png-54kB

Servlet生命週期

  1. init方法:當Servlet容器第一次載入並建立Servlet例項時,在呼叫該Servlet的建構函式後立即呼叫init方法對該Servlet物件進行初始化。構造器和init方法都只會被呼叫一次,這說明Servlet是單例項的(需要考慮執行緒安全的問題,不推薦在其中寫全域性變數)。
  2. service方法:每次請求都會呼叫service方法,它是實際用於響應請求的方法,可以被多次呼叫。
  3. destroy方法:在伺服器端停止且解除安裝Servlet時執行該方法,用於釋放當前Servlet所佔用的資源,只會被呼叫一次。

以上方法都由Servlet容器(例如Tomcat)負責呼叫。
 
 

ServletConfig

  Servlet介面的init方法會接收一個ServletConfig物件作為引數,即init(ServletConfig servletConfig),ServletConfig物件封裝了該Serlvet的配置資訊,並且可以獲取ServletContext物件。例如:
  
在web.xml中配置Servlet的初始化引數:

<servlet
>
<!-- Servlet註冊的名字 --> <servlet-name>helloServlet</servlet-name> <!-- Servlet全類名 --> <servlet-class>servlet.HelloServlet</servlet-class> <!-- 配置該Servlet的初始化引數 --> <init-param> <param-name
>
username</param-name> <param-value>root</param-value> </init-param> <init-param> <param-name>password</param-name> <param-value>123456</param-value> </init-param> </servlet>

可以在HelloServlet的init方法中獲取這些引數的資訊(在實際開發中,通常使用的Servlet都繼承了HttpServlet類,可以通過getServletConfig()在該Servlet物件中獲取到ServletConfig物件,不一定只能在init方法中使用,下面的getServletContext()也是一樣):

@Override
    public void init(ServletConfig servletConfig) throws ServletException {
        //獲取Servlet初始化引數相關的資訊
        String username = servletConfig.getInitParameter("username");
        System.out.println("username is "+username);
        Enumeration<String> names = servletConfig.getInitParameterNames();
        while(names.hasMoreElements()){
            String name = names.nextElement();
            System.out.println("name is "+name);
            System.out.println("value is "+servletConfig.getInitParameter(name));
        }

當有請求傳送到該Servlet後可以在控制檯看到輸出:

image_1b207k93m1f0knmd1vej8pcqlsm.png-17.4kB
 
 

ServletContext

  ServletContext物件代表著當前的WEB應用。可以認為SerlvetContext是當前 WEB應用的一個大管家,可以從中獲取到當前WEB應用的各個方面的資訊。
  ServletContext物件可以由SerlvetConfig獲取:

ServletContext servletContext = servletConfig.getServletContext();

  ServletContext主要的作用有:
  
① 獲取當前WEB應用的初始化引數:
  
在web.xml中配置當前WEB應用的初始化引數:(這些引數可以被所有的 Servlet通過ServletContext獲取,而上面配置在Servlet中的引數只能由對應的Servlet獲取)

<context-param>
        <param-name>driver</param-name>
        <param-value>com.mysql.jdbc.Driver</param-value>
    </context-param>
    <context-param>
        <param-name>jdbcUrl</param-name>
        <param-value>jdbc:mysql:///xiangwanpeng</param-value>
    </context-param>

在init方法中獲取到這些引數:

@Override
    public void init(ServletConfig servletConfig) throws ServletException {
        //獲取ServletContext物件,它包含了當前WEB應用的各種資訊
        ServletContext servletContext = servletConfig.getServletContext();
        //獲取WEB應用的初始化資訊,方法和servletConfig類似
        String driver = servletContext.getInitParameter("driver");
        System.out.println("driver is "+driver);
        Enumeration<String> names2 = servletContext.getInitParameterNames();
        while(names2.hasMoreElements()){
            String name2 = names2.nextElement();
            System.out.println("name2 is "+name2);
            System.out.println("value2 is "+servletContext.getInitParameter(name2));
        }
}

當有請求傳送到該Servlet後可以在控制檯看到輸出:

image_1b207q5r788mo0hf6m1gj1omo13.png-9.8kB

② 獲取當前WEB應用的某一個檔案在伺服器上的絕對路徑,而不是部署前的路徑。

例如現在在根目錄下新建一個note.txt:

image_1b209obula7s169r1501pn71a7k1t.png-5kB

在Servlet中獲取:

String realPath = servletContext.getRealPath("/note.txt");
        System.out.println("realPath is "+realPath);

獲取請求後在控制檯輸出:

realPath is G:\Eclipse\J2EE\workspace.metadata.plugins\org.eclipse.wst.server.core\tmp0\wtpwebapps\JavaWeb\note.txt

③ 獲取當前 WEB應用的名稱:

String contextPath = servletContext.getContextPath();
System.out.println(contextPath);

④ 獲取當前 WEB應用的某一個檔案對應的輸入流:

在src目錄下新建一個jdbc.properties:

image_1b20aapc615gg2qd4iv1ofebah2a.png-6.3kB

在Servlet中獲取該檔案的輸入流:

InputStream is = servletContext.getResourceAsStream("/WEB-INF/classes/jdbc.properties");
        System.out.println("is is "+is);

輸出:

image_1b20acpqjehe1b8lsrk1if4dtr2n.png-7.1kB

注意區別另一種方法,即通過getClassLoader獲取,這種方法的路徑是沒有/WEB-INF/classes/字首的:

InputStream is2 = this.getClass().getClassLoader().getResourceAsStream("jdbc.properties");
        System.out.println("is2 is "+is2);

⑤ 和attribute相關的幾個方法。
 
 

GenericServlet

  GenericServlet是Servlet介面和ServletConfig介面的實現類,是一個抽象類,因為其中的service()方法為抽象方法。它的作用是:如果新建的 Servlet程式直接繼承GenericSerlvet會使開發更簡潔。
  
具體實現:
① 在GenericServlet中聲明瞭一個SerlvetConfig型別的成員變數,在init(ServletConfig)方法中對其進行了初始化。
② 利用servletConfig成員變數的方法實現了 ServletConfig介面的方法。
③ 還定義了一個 init()方法,在init(SerlvetConfig)方法中對其進行呼叫,子類可以直接覆蓋init()在其中實現對Servlet的初始化。
④ 不建議直接覆蓋 init(ServletConfig),因為如果忘記編寫super.init(config),而還是用了SerlvetConfig介面的方法,則會出現空指標異常。
⑤ 新建的 init(){}並非Serlvet的生命週期方法,而init(ServletConfig)是生命週期相關的方法。

以下是原始碼:

public abstract class GenericServlet 
    implements Servlet, ServletConfig, java.io.Serializable
{
    private transient ServletConfig config;

    public GenericServlet() { }

    public void destroy() {
    }

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

    public Enumeration getInitParameterNames() {
    return getServletConfig().getInitParameterNames();
    }   

    public ServletConfig getServletConfig() {
    return config;
    }

    public ServletContext getServletContext() {
    return getServletConfig().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) {
    getServletContext().log(getServletName() + ": "+ msg);
    }

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

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

    public String getServletName() {
        return config.getServletName();
    }
}

HttpServlet

  HttpServlet是一個繼承自 GenericServlet的Servlet,而且它是針對於 HTTP協議所定製。
  HttpServlet在service(ServletRequest,ServletResponse)方法中直接把ServletReuqest和 ServletResponse強制轉換為HttpServletRequest和HttpServletResponse。並呼叫了過載的service(HttpServletRequest, HttpServletResponse)方法。
  在service(HttpServletRequest, HttpServletResponse)中,通過request.getMethod()獲取請求方式,並根據請求方式建立了doXxx()方法(xxx為具體的請求方式,比如 doGet,doPost)。
  實際開發中, 我們通常直接繼承HttpServlet,這樣做的好處是可以直接有針對性的覆蓋doXxx()方法;直接使用HttpServletRequest和HttpServletResponse,而不再需要進行型別的強制轉換。
  
主要原始碼如下:

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

    try {
        request = (HttpServletRequest) req;
        response = (HttpServletResponse) res;
    } catch (ClassCastException e) {
        throw new ServletException("non-HTTP request or response");
    }
    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) {
        // servlet doesn't support if-modified-since, no reason
        // to go through further expensive logic
        doGet(req, resp);
        } else {
        long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
        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 {
            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 {
        //
        // Note that this means NO servlet supports whatever
        // method was requested, anywhere on this server.
        //

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