簡介 ServletContext


ServletContext 介面定義了 Servlet 運行的應用程式環境的一些行為與觀點,你可以使用 ServletContext 實作物件來取得所請求資源的 URL、設定與儲存屬性、應用程式初始參數,甚至動態設定 Servlet 實例。

ServletContext 本身的名稱令人困惑,因為它以 Servlet 名稱作為開頭,容易被誤認為僅是單一 Servlet 的代表物件。事實上,當整個 Web 應用程式載入 Web 容器之後,容器會生成一個 ServletContext 物件作為整個應用程式的代表,並設定給 ServletConfig,你只要透過 ServletConfiggetServletContext() 方法就可以取得 ServletContext 物件。以下則先簡介幾個需要注意的方法:

getRequestDispatcher() 方法可以取得 RequestDispatcher 實例,使用時路徑的指定必須以 / 作為開頭,這個斜線代表應用程式環境根目錄(Context Root)。正如 調派請求 的說明,取得 RequestDispatcher 實例之後,就可以進行請求的轉發(Forward)或包含(Include)。

getServletContext().getRequestDispatcher("/pages/some.jsp").forward(request, response);

/ 作為開頭有時稱為環境相對(Context-relative)路徑,沒有以 / 作為開頭則稱為請求相對(Request-relative)路徑。

實際上 HttpServletRequestgetRequestDispatcher() 方法在實作時,若是環境相對路徑,則直接委託給 ServletContextgetRequestDispatcher();若是請求相對路徑,則轉換為環境相對路徑,再委託給 ServletContextgetRequestDispatcher() 來取得 RequestDispatcher

如果想要知道 Web 應用程式的某個目錄中有哪些檔案,則可以使用 getResourcePaths()方法。例如:

getServletContext().getResourcePaths("/")
                   .forEach(out::println);

使用時指定路徑必須以 / 作為開頭,表示相對於應用程式環境根目錄,傳回的路徑會像是:

/welcome.html
/catalog/
/catalog/index.html
/catalog/products.html
/customer/
/customer/login.jsp
/WEB-INF/
/WEB-INF/web.xml
/WEB-INF/classes/com.acme.OrderServlet.class

可以看到,這個方法會連同 WEB-INF 的資訊都列出來。如果是個目錄資訊,則會以 / 作結尾。以下這個範例利用了 getResourcePaths() 方法,自動取得 avatars 目錄下的圖片路徑,並透過 <img> 標籤來顯示圖片:

package cc.openhome;

import java.io.*;
import javax.servlet.*;
import javax.servlet.annotation.*;
import javax.servlet.http.*;

@WebServlet(
    urlPatterns = {"/avatar"},
    initParams = {
        @WebInitParam(name = "AVATAR_DIR", value = "/avatars")
    }
)
public class Avatar extends HttpServlet {
    private String AVATAR_DIR;

    @Override
    public void init() throws ServletException {
        super.init();
        AVATAR_DIR = getInitParameter("AVATAR_DIR");
    }

    protected void doGet(HttpServletRequest request,
            HttpServletResponse response)
            throws ServletException, IOException {
        response.setContentType("text/html;charset=UTF-8");

        PrintWriter out = response.getWriter();
        out.println("<!DOCTYPE html>");
        out.println("<html>");
        out.println("<body>");

        getServletContext().getResourcePaths(AVATAR_DIR)
                           .forEach(avatar -> {
                               out.printf("<img src='%s'>%n", avatar.replaceFirst("/", ""));
                           });

        out.println("</body>");
        out.println("</html>");
    }
}

如果想在 Web 應用程式中讀取某個檔案的內容,則可以使用 getResourceAsStream() 方法,使用時指定路徑必須以 / 作為開頭,表示相對於應用程式環境根目錄,或者相對是 /WEB-INF/lib 中 JAR 檔案裏 META-INF/resources 的路徑(Web應用程式中,JAR檔案必須放在 /WEB-INF/lib 中,這是規定),執行結果會傳回 InputStream 實例,接著你就可以運用它來讀取檔案內容。底下是個讀取 PDF 並傳送給客戶端的範例:

package cc.openhome;

import java.io.*;
import javax.servlet.*;
import javax.servlet.annotation.*;
import javax.servlet.http.*;

@WebServlet(urlPatterns={"/ebook"},
    initParams={
        @WebInitParam(name="PDF_FILE", value="/WEB-INF/jdbc.pdf")
    }
)
public class Ebook extends HttpServlet {
    private String PDF_FILE;

    @Override
    public void init() throws ServletException {
        super.init();
        PDF_FILE = getInitParameter("PDF_FILE");
    }

    protected void doGet(HttpServletRequest request,
                          HttpServletResponse response)
                      throws ServletException, IOException {
        String coupon = request.getParameter("coupon");
        if("123456".equals(coupon)) {
            response.setContentType("application/pdf");

            try(InputStream in = getServletContext().getResourceAsStream(PDF_FILE)) {
                OutputStream out = response.getOutputStream();
                byte[] buffer = new byte[1024];
                int length = -1;
                while((length = in.read(buffer)) != -1) {
                    out.write(buffer, 0, length);
                }
            }
        }
    }
}

正如在〈ServletConfig〉介紹過的,每個 Servlet 都會有一個相對應的 ServletConfig 物件,你可以透過 ServletConfiggetInitParameter() 方法來讀取初始參數,而每個 Web 應用程式都會有一個相對應的 ServletContext,針對「應用程式」初始化時所需用到的一些參數資料,你可以在 web.xm l中設定應用程式初始參數,設定時使用 <context-param> 標籤來定義。例如:

<web-app ...>
    <context-param>
       <param-name>MESSAGE</param-name>
       <param-value>/WEB-INF/messages.txt</param-value>
    </context-param>
    …
</web-app>

每一對初始參數要使用一個 <context-param> 來定義。之後在程式中可以透過 ServletContextgetInitParameter() 方法來讀取初始參數。因此 Web 應用程式初始參數常被稱為 ServletContext 初始參數。

在整個 Web 應用程式生命週期,Servlet 所需共用的資料,則可以設定為 ServletContext 屬性。由於 ServletContext 在 Web 應用程式存活期間都會一直存在,所以設定為 ServletContext 屬性的資料,除非你主動移除,否則也是一直存活於 Web 應用程式之中。

可以透過 ServletContextsetAttribute() 方法設定物件為 ServletContext 屬性,之後可透過 ServletContextgetAttribute() 方法取出該屬性。若要移除屬性,則透過 ServletContextremoveAttribute() 方法。

Web 容器會提供每個 ServletContext 一個暫存目錄,這個目錄的資訊可以藉由 ServletContextgetAttribute() 方法取得 "javax.servlet.context.tempdir" 屬性來得知,這個常數值可以透過 ServletContext.TEMPDIR 來取得。

注意!ServletContext 並非執行緒安全,所以必要時必須注意屬性設定時共用存取的問題。