Context 事件、傾聽器


ServletContext 相關的傾聽器有 ServletContextListenerServletContextAttributeListener

如果想要知道何時 Web 應用程式已經初始化或即將結束銷毀,你可以實作 ServletContextListener

package javax.servlet;

import java.util.EventListener;

public interface ServletContextListener extends EventListener {
    public default void contextInitialized(ServletContextEvent sce) {}
    public default void contextDestroyed(ServletContextEvent sce) {}
}

在 Web 應用程式初始化後或即將結束銷毀前,會呼叫 ServletContextListener 實作類別相對應的 contextInitialized()contextDestroyed()。你可以在 contextInitialized() 中實作應用程式資源的準備動作,在 contextDestroyed() 實作釋放應用程式資源的動作。在 Servlet 4.0 中,這兩個方法都是 default,對感興趣的方法實作即可。

例如說,你可以實作 ServletContextListener,在應用程式初始過程中,準備好資料庫連線物件、讀取應用程式設定等動作,像是放置使用頭像的目錄資訊,不宜將目錄資訊寫死在程式碼,以免日後目錄變動名稱或位置時,所有相關的 Servlet 都需要進行原始碼的修改,這時你可以這麼作:

package cc.openhome;
...
public class AvatarInitializer implements ServletContextListener {
    public void contextInitialized(ServletContextEvent sce) {
        ServletContext context = sce.getServletContext();
        String avatars = context.getInitParameter("AVATAR");
        context.setAttribute("avatars", avatars);
        ...
    }
    ...
}

當 Web 容器呼叫 contextInitialized()contextDestroyed() 時,會傳入 ServletContextEvent,其封裝了 ServletContext,你可以透過 ServletContextEventgetServletContext() 方法取得 ServletContext,之後就可以進行 ServletContext 初始參數的讀取。

你可以在web.xml中如下定義:

...
    <context-param>
        <param-name>AVATAR</param-name>
        <param-value>/avatars</param-value>
    </context-param>
    <listener>
        <listener-class>cc.openhome.AvatarInitializer</listener-class>
    </listener>
...

在 web.xml 中,使用 <context-param> 標籤來定義初始參數,並使用了 <listener><listener-class> 標籤來定義實作了 ServletContextListener 介面的類別名稱。ServletContextListener 也可以直接使用 @WebListener 標註,如此容器就會在啟動時載入並執行對應的方法,但因 @WebListener 沒有設定初始參數的屬性,所以僅適用於無需設置初始參數的情況。

有些應用程式的設定,必須在 Web 應用程式初始時進行,例如〈HttpSession 原理〉中談過,可以取得 ServletContext 而後進行 SessionCookieConfig 的設定,這就必須在應用程式初始時進行,例如,透過 SessionCookieConfig 來改變 Session ID:

...
@WebListener()
public class SomeContextListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        ServletContext context = sce.getServletContext();
        context.getSessionCookieConfig().setName("caterpillar-sessionId");
    }
}

若想透過 ServletContext 來動態部署 Servlet,底下是個例子:

package cc.openhome;

import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.ServletRegistration;
import javax.servlet.annotation.WebListener;

@WebListener
public class ResourceServletContextListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        ServletContext ctx = sce.getServletContext();
        ServletRegistration.Dynamic servlet = ctx.addServlet("HelloWorld", new HelloWorld());
        servlet.setLoadOnStartup(1);
        servlet.addMapping("/helloworld");
    }
}

在上例中,HelloWorld 類別是個簡單的 Servlet,不需要使用 @WebServlet 標註。

如果你想要物件被設置、移除或替換 ServletContext 屬性時,可以收到通知以進行一些動作,則可以實作 ServletContextAttributeListener

package javax.servlet;

import java.util.EventListener;

public interface ServletContextAttributeListener extends EventListener {
    public default void attributeAdded(ServletContextAttributeEvent scae) {}
    public default void attributeRemoved(ServletContextAttributeEvent scae) {}
    public default void attributeReplaced(ServletContextAttributeEvent scae) {}
}

當你在 ServletContext 中加入屬性、移除屬性或替換屬性時,相對應的 attributeAdded()attributeRemoved()attributeReplaced() 方法就會被呼叫。

如果希望容器在部署應用程式時,實例化實作 ServletContextAttributeListener 的類別並註冊給應用程式,則同樣也是在實作類別上標註 @WebListener

package cc.openhome;
...
@WebListener()
public class SomeContextAttrListener
               implements ServletContextAttributeListener {
    @Override
    public void attributeAdded(ServletContextAttributeEvent scab) {
        //...
    }

    @Override
    public void attributeRemoved(ServletContextAttributeEvent scab) {
        //...
    }

    @Override
    public void attributeReplaced(ServletContextAttributeEvent scab) {
        //...
    }
}

另一個方式是在 web.xml 下如下設定:

<web-app...>
    ...
    <listener>
        <listener-class>cc.openhome.SomeContextAttrListener</listener-class>
    </listener>
   ...
<web-app>