1. 程式人生 > >Java Servlet 實戰入門教程-07-servlet ServletContext 上下文詳解

Java Servlet 實戰入門教程-07-servlet ServletContext 上下文詳解

ServletContext

定義

定義servlet用於與其servlet容器通訊的一組方法,例如,獲取檔案的MIME型別、分派請求或寫入日誌檔案。

對於每個Java虛擬機器,每個“web應用程式”都有一個上下文。(“web應用程式”是servlet和內容的集合,安裝在伺服器URL名稱空間(如/catalog)的特定子集下,可能通過.war檔案安裝。)

對於在部署描述符中標記為“分散式”的web應用程式,每個虛擬機器都有一個上下文例項。在這種情況下,上下文不能用作共享全域性資訊的位置(因為這些資訊不是真正的全域性資訊)。使用外部資源,如資料庫。

ServletContext物件包含在ServletConfig物件中,Web伺服器在servlet初始化時提供servlet。

建立時機

容器啟動的時候,併為其提供Servlet初始化引數的名/值對的引用。

獲取方式

在Servlet中可以通過兩種方式獲取到ServletConfig:

  1. 通過過載的 init() 初始化方法引數中直接獲取。

  2. 直接呼叫 getServletConfig() 方法(繼承自GenericServlet)

方法

得到初始化引數和獲取/設定屬性

如下 ServletContext 介面方法允許 servlet 訪問由應用開發人員在Web 應用中的部署描述符中指定的上下文初始化引數:

應用開發人員使用初始化引數來表達配置資訊。代表性的例子是一個網路管理員的 e-mail 地址,或儲存關鍵資料的系統名稱。

  1. getInitParameter(String)

  2. getInitParameterNames()

操作上下文屬性

  1. getAttribute(String)

  2. getAttributeNames()

  3. setAttribute(String,Object)

  4. removeAttribute(String)

得到有關伺服器(及容器)資訊

  1. getMajorVersion()

  2. getServerInfo()

訪問資原始檔

  1. getResource(String parh)

  2. getResourceAsStream(String parh)

getResource 和 getResourceAsStream 方法需要一個以 /

開頭的String 作為引數,給定的資源路徑是相對於上下文的根,或者相對於 web應用的 WEB-INF/lib 目錄下的 JAR 檔案中的 META-INF/resources 目錄。

這兩個方法首先根據請求的資源查詢 web 應用上下文的根,然後查詢所有 WEB-INF/lib 目錄下的 JAR 檔案。查詢 WEB-INF/lib 目錄中 JAR檔案的順序是不確定的。

這種層次結構的檔案可以存在於伺服器的檔案系統,Web 應用的歸檔檔案,遠端伺服器,或在其他位置。

這兩個方法不能用於獲取動態內容。例如,在支援 JavaServer Pages™規範的容器中,如 getResource("/index.jsp") 形式的方法呼叫將返回 JSP 原始碼而不是處理後的輸出。

實現Servlet的轉發

context.getRequestDispatcher("/index.jsp").forword(request, response);  

記錄伺服器(如tomcat)日誌檔案。

  1. log(String msg)

此處可以使用 Slf4j 等成熟的日誌框架。

初始化引數

  1. getInitParameter

  2. getInitParameterNames

過載注意事項

儘管 Container Provider (容器供應商)不需要實現類的重載入模式以便易於開發,但是任何此類的實現必須確保所有 servlet 及它們使用的類(Servlet使用的系統類異常可能使用的是一個不同的 class loader)在一個單獨的 class loader 範圍內被載入。

為了保證應用像開發人員預期的那樣工作,該要求是必須的。作為一個開發輔助,容器應支援到會話繫結到的監聽器的完整通知語義以用於當 class 重載入時會話終結的監控。

之前幾代的容器建立新的 class loader 來載入 servlet,且與用於載入在 servlet 上下文中使用的其他 servlet 或類的 class loader 是完全不同的。這可能導致 servlet 上下文中的物件引用指向意想不到的類或物件,並引起意想不到的行為。為了防止因建立新的 class loader 所引起的問題,該要求是必須的。

臨時工作目錄

每一個 servlet 上下文都需要一個臨時的儲存目錄。

servlet 容器必須為每一個 servlet 上下文提供一個私有的臨時目錄,並將通過javax.servlet.context.tempdir 上下文屬性使其可用,關聯該屬性的物件必須是 java.io.File 型別。

該要求公認為在多個 servlet 引擎實現中提供一個通用的便利。

當 servlet 容器重啟時,它不需要去保持臨時目錄中的內容,但必須確保一個 servlet 上下文的臨時目錄中的內容對執行在同一個 servlet 容器的其他 Web 應用的上下文不可見。

實際案例

初始化引數

  • web.xml

配置如下:

<context-param>
    <param-name>global-email</param-name>
    <param-value>www.google.com</param-value>
</context-param>
  • ContextXmlServlet.java

在 Servlet 類中取得 context 中指定的屬性。

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet("/context/xml")
public class ContextXmlServlet extends HttpServlet {

    private static final long serialVersionUID = -4188524517356470483L;

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        final String email = getServletContext().getInitParameter("global-email");
        PrintWriter printWriter = resp.getWriter();
        printWriter.write("Email: " + email);
    }
}

屬性設定

此處為了方便,將設定和獲取放在了一起。

實際使用過程中,設定和獲取可以是不同的 Servlet。

@WebServlet("/context/attr")
public class ContextAttributeServlet extends HttpServlet {
    private static final long serialVersionUID = 1363903348852718740L;

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //1. 設定屬性
        final String attrKey = "name";
        final String randomValue = UUID.randomUUID().toString();
        getServletContext().setAttribute(attrKey, randomValue);

        //2. 獲取屬性 實際使用時可以是不同的 Servlet
        final String attrValue = getServletContext().getAttribute(attrKey).toString();
        PrintWriter printWriter = resp.getWriter();
        printWriter.write("Attr: " + attrValue);
    }
}

網頁計數器

  • ContextPageCounterServlet.java
import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet("/context/counter")
public class ContextPageCounterServlet extends HttpServlet {

    private static final long serialVersionUID = 2853152264384162008L;

    /**
     * 點選總數
     */
    private static final String HIT_COUNT = "hit.count";

    /**
     * 點選總數
     * 實際使用 可以從資料庫等地方讀取這個數值
     */
    private static int countNum = 0;

    @Override
    public void init(ServletConfig config) throws ServletException {
        countNum = 0;
        super.init(config);
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 設定響應內容型別為純文字,編碼為 UTF8
        resp.setContentType("text/plain;charset=UTF-8");
        PrintWriter printWriter = resp.getWriter();
        ServletContext servletContext = getServletContext();

        countNum++;
        // 其他的 servlet 也可以獲取並且更新這個點選資訊
        servletContext.setAttribute(HIT_COUNT, countNum);
        printWriter.write("點選總數:" + countNum);
    }

    @Override
    public void destroy() {
        //持久化儲存點選總數
    }
}
  • 過濾器

當然這種方式如果我想攔截很多 Servlet,一個個寫未免太愚笨了些。

可以參見 過濾器

參考資料