天天看點

Servlet--Servlet進階API、過濾器、監聽器

Servlet初始化過程、ServletConfig

每個Servlet都必須由Web容器讀取Servlet設定的資訊,初始化等,才能生成對應的Servlet執行個體。對于每個Servlet的設定資訊,Web容器都會為其生成一個ServletConfig作為代表對象。

在Servlet接口上,定義了與Servlet生命周期及請求服務相關的init,service,destroy三個方法。每一次請求來到容器時,會産生HttpServletRequest,HttpServletResponse對象,并調用service方法時當做參數傳入。

在Web容器啟動後,根據上面所說,會産生一個ServletConfig對象,而後調用init方法并将産生的ServletConfig對象傳入其中,這個過程在建立Servlet執行個體之後隻會發生一次,之後每次請求到來,就隻調用Servlet的service方法進行服務。

Servlet類架構圖:

Servlet--Servlet進階API、過濾器、監聽器

在Java中,當我們想要在對象執行個體化後做一些操作,必須定義構造器,然而在JavaWeb中則不然,當我們想要使用ServletConfig來做一些事情的時候,我們需要重新定義init方法,因為在Web中,對象執行個體化之後,容器還沒有調用init方法傳入ServletConfig,是以我們如果在構造器中使用ServletConfig的話,是沒有這個對象的。

在之前我們要得到我們在Servlet中設定的參數的有關資訊(WebServlet中設定的東西),我們是直接使用getInitParameterNames, getInitParameterName等方法,其實這些方法都是經過封裝的,目的就是不讓我們意識到ServletConfig這一API,實際上,GenericServlet中包括了Servlet和ServletConfig所定義方法的簡單實作,如:

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

等等。是以一般我們在取得Servlet的初始參數時候,都會直接将代碼寫成getInitParameter。

根據上面我所說的,因為ServletConfig是在容器調用有參數的init方法的時候才被調用的,是以我們如果要使用ServletConfig進行一些初始化的動作,我們需要在無參數的init方法中進行定義,我們來看一下源碼:

private transient ServletConfig config;
public void init(ServletConfig config) throw ServletException {
    this.config = config;
    this.init();
}
           

我們可以清楚的看到在init中定義的一系列初始化動作,在得到ServletConfig的執行個體之後都會得到執行。

至于在Servlet中什麼時候使用初始化參數,我覺得它的作用是和C語言的#define相當的。

ServletContext

ServletContext對象可以用來取得請求資源的URL,設定與存儲屬性,應用程式初始化參數,動态設定Servlet執行個體。

ServletContext事實上不是單一Servlet的代表對象,當整個Web應用程式加載Web容器之後,容器會産生一個ServletContext對象作為整個應用程式的代表,并設定給ServletConfig,通過ServletConfig的getServletContext方法可以取得ServletContext對象。

getRequestDispatcher()

用來取得RequestDispatcher執行個體,使用時的路徑必須指定為“/”作為開頭,這個“/”代表應用程式環境跟目錄。

以“/”作為開頭稱為環境相對路徑,沒有以“/”作為開頭稱為請求相對路徑。實際上getRequestDispatcher在實作的時候,若是環境相對路徑,則直接委托給ServletContext的getRequestDispatcher,若是請求相對路徑,則直接轉換為環境相對路徑,在委托給ServletContext的getRequestDispatcher方法。

getResourcePaths()

它可以幫助我們知道Web應用程式的某個目錄中有哪些檔案,使用時的路徑必須指定為“/”作為開頭,表示相對于應用程式環境根目錄。

getResourceAsStream()

如果想在Web應用程式中讀取某個檔案的内容,則可以使用getResourceAsStream(),它的指定路徑也是以“/”開頭,運作結果會傳回InputStream執行個體。

ServletContext事件,監聽器

監聽器:當我們想要在HttpServletRequest,HttpSession,ServletContext對象生成,銷毀或相關屬性發生的時間點做一些我們想要做的事情,就要實作相對應的監聽器。

ServletContextListener

ServletContextListener是“生命周期監聽器”,如果想要知道Web應用程式何時已經初始化,或即将銷毀,可以實作這個類。

在這個接口中,定義了兩個方法,分别是:

public interface ServletContextListen extends EventListener {
    public void contextInitialized(ServletContextEvent sce);
    public void contextDestroyed(ServletContextEvent sce);
}
           

在Web應用程式初始化後或即将結束銷毀之前,會調用ServletContextListener實作類相對應的contextInitialized方法或contextDestroyed方法。可以在contextInitialized中實作應用程式初始化後資源的準備動作,在contextDestroyed中實作應用程式資源的釋放動作。

ServletContextListener可以直接使用@WebListener标注,而且必須實作ServletContextListener接口。當容器調用這個接口中的方法時,會傳入ServletContextEvent,其封裝了ServletContext,可以通過ServletContextEvent的getServletContext方法取得ServletContext,通過ServletContext的getInitParameter方法來讀取初始參數。

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

因為@WebListener沒有設定初始參數的屬性,是以僅适用與無須設定初始參數的情況。如果需要設定初始參數,可以再Web.xml中設定。

<context-param>
    <param-name>AVATAR</param-name>
    <param-value>/avatars</param-value>
</context-param>
           

ServletContextAttributeListener

此類是屬性改變監聽器,如果想要對象被設定,移除,或替換ServletContext屬性,可以收到通知以進行一些操作,可以實作這個類。

HttpSession事件、監聽器

HttpSessionListener

生命周期監聽器,與ServletContext生命周期監聽器功能相近,如果想在HttpSession對象建立或結束時,做一些相對應的動作,可以實作此類。

public interface HttpSessionListener extends EventListener {
    public void sessionCreated(HttpSessionEvent se);
    public void sessionDestroyed(HttpSessionEvent se);
}
           

方法的具體功能我不在細說,對照ServletContextListener就行,我們可以通過傳入的HttpSessionEvent,使用getSession得到HttpSession,以針對會話對象做出相對應的建立或結束處理操作 。

現如今有一個真實的應用場景,有些網站為了防止使用者重複登入,會在資料庫中以某個字段代表使用者是否登入,使用者登入之後在資料庫中設定該字段資訊,代表使用者已經登入,而使用者登出之後則重置該字段。如果使用者已經登入,在登出之前嘗試再用另一個浏覽器進行登入,應用程式會檢查資料中代表登入與否的字段,如果發現已被設定為登入,則拒絕重複登入。現在有一個問題,如果使用者在進行登出之前不小心直接關閉了浏覽器,沒有确實運作登出的操作,那麼資料庫中代表登入與否的字段就不會被重置。為此可以實作HttpSessionListener,由于HttpSession有存活期限,當容器銷毀某個HttpSession時,就會調用sessionDestoryed,我們就可以在其中重置資料庫的登入字段。

HttpSessionAttributeListener

這個類的功能和ServletContextAttributeListener是非常相近的,我們直接來看一下該接口的源碼:

public interface HttpSessionAttributeListener extends EventListener {
    public void attributeAdded (HttpSessionBindEvent se);
    public void attributeRemoved (HttpSessionBindEvent se);
    public void attributeReplaced (HttpSessionBindEvent se);
}
           

HttpSessionBindListener

對象綁定監聽器,如果有個即将加入HttpSession的屬性對象,希望在設定給HttpSession成為屬性或從HttpSession中一處時,可以收到HttpSession的通知,則可以讓該對象實作HttpSessionBindListener接口。看到這,大家有沒有對這個類和上一個類産生混淆,乍一看,這兩個類實作的是一樣的功能啊。我也是在網上搜尋了一些資料後,才解開了心中的迷惑。我們在使用HttpSessionBindListener的時候,想要将一個對象設定為HttpSession屬性并對其進行“監聽”,那麼我們必須在實作這個類的時候讓其實作HttpSessionBindListener這個接口,然後綁定到HttpSession上才能實作監聽。然而當我們使用HttpSessionAttributeListener接口時,無論任何對象,隻要我們将其綁定到HttpSession上,就可以對其進行“監聽”。

package SessionListenerDemo2;

import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionBindingListener;

/**
 * Created by hg_yi on 17-6-5.
 */
public class User implements HttpSessionBindingListener {
    private String name;
    private String data;

    public User(String name) {
        this.name = name;
    }

    public void valueBound(HttpSessionBindingEvent event) {
        this.data = name + "來自資料庫的操作...";
    }

    public void valueUnbound(HttpSessionBindingEvent event) { }

    public String getData() {
        return data;
    }

    public String getName() {
        return name;
    }
}
           

當我們想要使用HttpSessionBindListener對User進行監聽,那麼我們在設定其為HttpSession屬性的時候,就需要讓其實作HttpSessionBindListener接口。

HttpServletRequest事件、監聽器

ServletRequestListener

生命周期監聽器,聽到這個名字,我想大家都已經知道它是幹什麼的了,直接來看一下源碼:

public interface ServletRequestListener extends EventListener {
    public void requestDestroyed(ServletRequestEvent sre);
    public void requestInitialized(ServletRequestEvent sre);
}
           

ServletRequestAttributeListener

屬性改變監聽器,其作用跟上面所說沒有什麼差別,方法中傳入的參數是ServletRequestAttributeEvent。

ServletRequestAttributeEvent有個getName方法,其作用是可以取得屬性設定或移除時指定的名稱,而getValue則可以取得屬性設定或移除時的對象。

生命周期監聽器,屬性改變監聽器,都必須使用@WebListener或在web.xml中設定,容器才會知道要加載,讀取監聽器相關設定。

過濾器

在說過濾器之前,我們先明确一個概念:

在容器調用Servlet的service方法之前,Servlet并不知道有請求的到來,調用service方法之後,在對浏覽器進行響應之前,浏覽器也不知道其真正的響應是什麼。基于這種特性,過濾器就是介于Servlet之前,可攔截浏覽器對Servlet的請求,也可改變Servlet對浏覽器的響應。

實作與設定過濾器

在Servlet/JSP中要實作過濾器,必須實作Filter接口,并使用@WebFilter标注或在web.xml中定義過濾器。filter源碼:

public interface Filter {
    public void init(FilterConfig filterConfig) throws ServletException;
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 
    throws IOException, ServletException;
    public void destroy();
}
           

可以發現其中有三個待實作的方法。下來我們來說一下這三個方法:

  • init:當過濾器類被載入容器時并執行個體化後,容器會運作其init方法并傳入FilterConfig對象作為參數,這個對象的作用和ServletConfig的作用是很一樣的,得到過濾器的設定資訊代表對象。
  • doFilter:容器在調用Servlet的service方法方法前,可以應用某過濾器,就會調用該過濾器的doFilter方法。在doFilter方法中可以進行service方法的前置處理,之後決定是否調用FilterChain的doFilter方法。如果調用了FilterChain的doFilter方法,就會運作下一個過濾器,如果沒有下一個過濾器,就調用請求目标的Servlet的service方法,如果因為某個情況沒有調用FilterChain的doFilter方法,則請求就不會繼續交給接下來的過濾器或目标Servlet,這就是攔截請求,從Servlet的觀點來看,根本不知道浏覽器有送出請求。
  • 如果想在過濾器銷毀之前做一些處理,則可以将動作定義到destroy方法中。

我們可以看一下FilterChain的doFilter的實作:

Filter filter = filterIterator.next();
if (filter != NULL) {
    filter.doFilter(request, response, this);
} else {
    targetServlet.service(request, response);
}
           

它的實作方式是遞歸,也就是說,service的後續處理會以堆棧的順序傳回。

觸發過濾器的時機,預設是浏覽器直接送出請求,如果是那些通過RequestDispatcher的forward或include的請求,設定@WebFilter的DispatcherTypes。例如:

@WebFilter(
    filterName = "some",
    urlPattern = {"/some"},
    dispatcherTypes = {
        DispatcherType.FORWARD,
        DispatcherType.INCLUDE,
        DispatcherType.REQUEST,
        DispatcherType.ERROR
    }
           

如果不設定任何dispatcherTypes,則預設為REQUEST。FORWARD就是指通過RequestDispatcher的forward而來的請求可以套用過濾器,其他的概念和這個基本相似。

如果有某個URL或Servlet會應用多個過濾器,則根據在web.xml中出現的先後順序,來決定過濾器的運作順序。