Servlet底層原理、Servlet實現方式、Servlet生命周期


Servlet簡介

Servlet定義

Servlet是一個Java應用程序,運行在服務器端,用來處理客戶端請求並作出響應的程序。

Servlet的特點

(1)Servlet對像,由Servlet容器(Tomcat)創建。

(2)Servlet是一個接口:位於javax.servlet包中。

(3)service方法用於接收用戶的請求並返回響應。

(4)用戶訪問時多次被執行(可以統計網站的訪問量)。

Servlet底層原理

Servlet

package javax.servlet;

import java.io.IOException;

public interface Servlet {
    void init(ServletConfig var1) throws ServletException;

    ServletConfig getServletConfig();

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

    String getServletInfo();

    void destroy();
}

我們可以看出Servlet實際上是一個接口,其中包含init、getServletConfig、service、getServletInfo、destroy幾個接口方法。

實現Servlet接口

package cn.chenhao;

import java.io.IOException;

import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

public class FirstServlet implements Servlet {
    /**init方法*/
    @Override
    public void init(ServletConfig paramServletConfig) throws ServletException {
    }

    /**getServletConfig方法*/
    @Override
    public ServletConfig getServletConfig() {
        return null;
    }

    /**service方法*/
    @Override
    public void service(ServletRequest paramServletRequest,
            ServletResponse paramServletResponse) throws ServletException,
            IOException {
    }

    /**getServletInfo方法*/
    @Override
    public String getServletInfo() {
        return null;
    }

    /**destroy方法*/
    @Override
    public void destroy() {
    }
}

我們需要實現servlet接口里面所有的方法,這對於我們開發來說是很繁瑣的。


Servlet 生命周期的方法: 以下方法都是由 Serlvet 容器負責調用.
1). 構造器: 只被調用一次. 只有第一次請求 Servlet 時, 創建 Servlet 的實例. 調用構造器. 這說明 Serlvet 的單實例的!
2). init 方法: 只被調用一次. 在創建好實例后立即被調用. 用於初始化當前 Servlet.
3). service: 被多次調用. 每次請求都會調用 service 方法. 實際用於響應請求的.
4). destroy: 只被調用一次. 在當前 Servlet 所在的 WEB 應用被卸載前調用. 用於釋放當前 Servlet 所占用的資源.

下面兩個方法是開發者調用
1). getServletConfig: 返回一個ServletConfig對象,其中包含這個servlet初始化和啟動參數.
2). getServletinfo: 返回有關servlet的信息,如作者、版本和版權.

在 web.xml 文件中配置和映射這個 Servlet

<!-- 配置和映射 Servlet -->
<servlet>
    <!-- Servlet 注冊的名字 -->
    <servlet-name>helloServlet</servlet-name>
    <!-- Servlet 的全類名 -->
    <servlet-class>com.atguigu.javaweb.HelloServlet</servlet-class>
</servlet>

<servlet-mapping>
    <!-- 需要和某一個 servlet 節點的 serlvet-name 子節點的文本節點一致 -->
    <servlet-name>helloServlet</servlet-name>
    <!-- 映射具體的訪問路徑: / 代表當前 WEB 應用的根目錄. -->
    <url-pattern>/hello</url-pattern>
</servlet-mapping>

 

load-on-startup 參數

1). 配置在 servlet 節點中:

<servlet>
    <!-- Servlet 注冊的名字 -->
    <servlet-name>secondServlet</servlet-name>
    <!-- Servlet 的全類名 -->
    <servlet-class>com.atguigu.javaweb.SecondServlet</servlet-class>
    <!-- 可以指定 Servlet 被創建的時機 -->
    <load-on-startup>2</load-on-startup>
</servlet>

2). load-on-startup: 可以指定 Serlvet 被創建的時機. 若為負數, 則在第一次請求時被創建.若為 0 或正數, 則在當前 WEB 應用被Serlvet 容器加載時創建實例, 且數組越小越早被創建.

關於 serlvet-mapping

1). 同一個Servlet可以被映射到多個URL上,即多個 <servlet-mapping> 元素的<servlet-name>子元素的設置值可以是同一個
Servlet的注冊名。

2). 在Servlet映射到的URL中也可以使用 * 通配符,但是只能有兩種固定的格式:
一種格式是“*.擴展名”,另一種格式是以正斜杠(/)開頭並以“/*”結尾。

<servlet-mapping>
    <servlet-name>secondServlet</servlet-name>
    <url-pattern>/*</url-pattern>
</servlet-mapping>

OR

<servlet-mapping>
    <servlet-name>secondServlet</servlet-name>
    <url-pattern>*.do</url-pattern>
</servlet-mapping>

注意: 以下的既帶 / 又帶擴展名的不合法. 

<servlet-mapping>
    <servlet-name>secondServlet</servlet-name>
    <url-pattern>/*.action</url-pattern>
</servlet-mapping>

Servlet生命周期

實例化:在第一次訪問或啟動tomcat時,tomcat會調用此無參構造方法實例化servlet。

初始化:tomcat在實例化此servlet后,會立即調用init方法初始化servlet。

就緒:容器收到請求后調用servlet的service方法來處理請求。

銷毀:容器依據自身算法刪除servlet對象,刪除前會調用destory方法

其中實例化,初始化,銷毀只會執行一次,service方法執行多次,默認情況下servlet是在第一次接受到用戶請求的情況下才會實例化,可以在web.xml中的<servlet><servlet>標簽內添加一個<load-on-startup>1<load-on-startup>配置,此時在啟動tomcat時會創建servlet實例。

ServletConfig

封裝了 Serlvet 的配置信息, 並且可以獲取 ServletContext 對象

1). 配置 Serlvet 的初始化參數

<servlet>
    <servlet-name>helloServlet</servlet-name>
    <servlet-class>com.atguigu.javaweb.HelloServlet</servlet-class>
    
    <!-- 配置 Serlvet 的初始化參數。 且節點必須在 load-on-startup 節點的前面 -->
    <init-param>
        <!-- 參數名 -->
        <param-name>user</param-name>
        <!-- 參數值 -->
        <param-value>root</param-value>
    </init-param>
    
    <init-param>
        <param-name>password</param-name>
        <param-value>1230</param-value>
    </init-param>
    
    <load-on-startup>-1</load-on-startup>
    
</servlet>

2). 獲取初始化參數: 

getInitParameter(String name): 獲取指定參數名的初始化參數
getInitParameterNames(): 獲取參數名組成的 Enumeration 對象. 

String user = servletConfig.getInitParameter("user");
System.out.println("user: " + user);

Enumeration<String> names = servletConfig.getInitParameterNames();
while(names.hasMoreElements()){
    String name = names.nextElement();
    String value = servletConfig.getInitParameter(name);
    System.out.println("^^" + name + ": " + value);
}

ServletContext

1). 可以由 SerlvetConfig 獲取:

ServletContext servletContext = servletConfig.getServletContext();

2). 該對象代表當前 WEB 應用: 可以認為 SerlvetContext 是當前 WEB 應用的一個大管家. 可以從中獲取到當前 WEB 應用的各個方面的信息.

①. 獲取當前 WEB 應用的初始化參數

設置初始化參數: 可以為所有的 Servlet 所獲取, 而 Servlet 的初始化參數只用那個 Serlvet 可以獲取.

<!-- 配置當前 WEB 應用的初始化參數 -->
<context-param>
  <param-name>driver</param-name>
  <param-value>com.mysql.jdbc.Driver</param-value>
</context-param>

代碼:

ServletContext servletContext = servletConfig.getServletContext();
String driver = servletContext.getInitParameter("driver");
System.out.println("driver:" + driver);

Enumeration<String> names2 = servletContext.getInitParameterNames();
while(names2.hasMoreElements()){
  String name = names2.nextElement();
  System.out.println("-->" + name); 
}

②. 獲取當前 WEB 應用的某一個文件在服務器上的絕對路徑, 而不是部署前的路徑

String realPath = servletContext.getRealPath("/note.txt");

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

String contextPath = servletContext.getContextPath();

④. 獲取當前 WEB 應用的某一個文件對應的輸入流. 

InputStream is2 = servletContext.getResourceAsStream("/WEB-INF/classes/jdbc.properties");

如何在 Serlvet 中獲取請求信息

1). Servlet 的 service() 方法用於應答請求: 因為每次請求都會調用 service() 方法

public void service(ServletRequest request, ServletResponse response)throws ServletException, IOException

ServletRequest: 封裝了請求信息. 可以從中獲取到任何的請求信息.
ServletResponse: 封裝了響應信息, 如果想給用戶什么響應, 具體可以使用該接口的方法實現.

這兩個接口的實現類都是服務器給予實現的, 並在服務器調用 service 方法時傳入.

2). ServletRequest: 封裝了請求信息. 可以從中獲取到任何的請求信息.

①. 獲取請求參數:

> String getParameter(String name): 根據請求參數的名字, 返回參數值. 

若請求參數有多個值(例如 checkbox), 該方法只能獲取到第一個提交的值.

> String[] getParameterValues(String name): 根據請求參數的名字, 返回請求參數對應的字符串數組.

> Enumeration getParameterNames(): 返回參數名對應的 Enumeration 對象, 
類似於 ServletConfig(或 ServletContext) 的 getInitParameterNames() 方法.

> Map getParameterMap(): 返回請求參數的鍵值對: key: 參數名, value: 參數值, String 數組類型.

②. 獲取請求的 URI:

HttpServletRequest httpServletRequest = (HttpServletRequest) request;
    
String requestURI = httpServletRequest.getRequestURI();
System.out.println(requestURI); //  /day_29/loginServlet

③. 獲取請求方式: 

String method = httpServletRequest.getMethod();
System.out.println(method); //GET

④. 若是一個 GET 請求, 獲取請求參數對應的那個字符串, 即 ? 后的那個字符串. 

String queryString = httpServletRequest.getQueryString();
System.out.println(queryString); //user=atguigu&password=123456&interesting=game&interesting=party&interesting=shopping

⑤. 獲取請求的 Serlvet 的映射路徑 

String servletPath = httpServletRequest.getServletPath();
System.out.println(servletPath);  //  /loginServlet

 

3). HttpServletRequest: 是 SerlvetRequest 的子接口. 針對於 HTTP 請求所定義. 里邊包含了大量獲取 HTTP 請求相關的方法. 

4). ServletResponse: 封裝了響應信息, 如果想給用戶什么響應, 具體可以使用該接口的方法實現. 

  ①. *getWriter(): 返回 PrintWriter 對象. 調用該對象的 print() 方法, 將把 print() 中的參數直接打印到客戶的瀏覽器上. 

  ②. 設置響應的內容類型: response.setContentType("application/msword");

  ③. void sendRedirect(String location): 請求的重定向. (此方法為 HttpServletResponse 中定義.)

 

GenericServlet

1). 是一個 Serlvet. 是 Servlet 接口和 ServletConfig 接口的實現類. 但是一個抽象類. 其中的 service 方法為抽象方法

2). 如果新建的 Servlet 程序直接繼承 GenericSerlvet 會使開發更簡潔.

3). 具體實現:

  ①. 在 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 {

    /** 以下方法為 Servlet 接口的方法 **/
    @Override
    public void destroy() {}

    @Override
    public ServletConfig getServletConfig() {
        return servletConfig;
    }

    @Override
    public String getServletInfo() {
        return null;
    }

    private ServletConfig servletConfig;
    
    @Override
    public void init(ServletConfig arg0) throws ServletException {
        this.servletConfig = arg0;
        init();
    }

    public void init() throws ServletException{}

     /**【注意】唯獨service方法沒有被實現,還是一個抽象方法,這個service方法必須我們自己去重寫*/
    @Override
    public abstract void service(ServletRequest arg0, ServletResponse arg1)
            throws ServletException, IOException;

    /** 以下方法為 ServletConfig 接口的方法 **/
    @Override
    public String getInitParameter(String arg0) {
        return servletConfig.getInitParameter(arg0);
    }

    @Override
    public Enumeration getInitParameterNames() {
        return servletConfig.getInitParameterNames();
    }

    @Override
    public ServletContext getServletContext() {
        return servletConfig.getServletContext();
    }

    @Override
    public String getServletName() {
        return servletConfig.getServletName();
    }
}
【注意】唯獨service方法沒有被實現,還是一個抽象方法,這個service方法必須我們自己去重寫。

HttpServlet

  1). 是一個 Servlet, 繼承自 GenericServlet. 針對於 HTTP 協議所定制.

  2). 在 service() 方法中直接把 ServletReuqest 和 ServletResponse 轉為 HttpServletRequest 和 HttpServletResponse.並調用了重載的 service(HttpServletRequest, HttpServletResponse)

      在 service(HttpServletRequest, HttpServletResponse) 獲取了請求方式: request.getMethod(). 根據請求方式有創建了doXxx() 方法(xxx 為具體的請求方式, 比如 doGet, doPost)

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

public void service(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {
    //1. 獲取請求方式.
    String method = request.getMethod();
    
    //2. 根據請求方式再調用對應的處理方法
    if("GET".equalsIgnoreCase(method)){
        doGet(request, response);
    }else if("POST".equalsIgnoreCase(method)){
        doPost(request, response);
    }
}

public void doPost(HttpServletRequest request, HttpServletResponse response) 
        throws ServletException, IOException{
    // TODO Auto-generated method stub
    
}

public void doGet(HttpServletRequest request, HttpServletResponse response) 
        throws ServletException, IOException {
    // TODO Auto-generated method stub
    
}

3). 實際開發中, 直接繼承 HttpServlet, 並根據請求方式復寫 doXxx() 方法即可.

4). 好處: 直接由針對性的覆蓋 doXxx() 方法; 直接使用 HttpServletRequest 和 HttpServletResponse, 不再需要強轉.

Servlet的生命周期

Servlet生命周期圖

Servlet生命周期簡述

(1)加載和實例化

  當Servlet容器啟動或客戶端發送一個請求時,Servlet容器會查找內存中是否存在該Servlet實例,若存在,則直接讀取該實例響應請求;如果不存在,就創建一個Servlet實例。

(2) 初始化

  實例化后,Servlet容器將調用Servlet的init()方法進行初始化(一些准備工作或資源預加載工作)。

(3)服務

  初始化后,Servlet處於能響應請求的就緒狀態。當接收到客戶端請求時,調用service()的方法處理客戶端請求,HttpServlet的service()方法會根據不同的請求 轉調不同的doXxx()方法,比如 doGet, doPost。

(4)銷毀

  當Servlet容器關閉時,Servlet實例也隨時銷毀。其間,Servlet容器會調用Servlet 的destroy()方法去判斷該Servlet是否應當被釋放(或回收資源)。

 

其中實例化,初始化,銷毀只會執行一次,service方法執行多次,默認情況下servlet是在第一次接受到用戶請求的情況下才會實例化,可以在web.xml中的<servlet><servlet>標簽內添加一個<load-on-startup>1<load-on-startup>配置,此時在啟動tomcat時會創建servlet實例。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM