Servlet過濾器是 Servlet 程序的一種特殊用法,主要用來完成一些通用的操作,如編碼的過濾、判斷用戶的登錄狀態。過濾器使得Servlet開發者能夠在客戶端請求到達 Servlet資源之前被截獲,在處理之后再發送給被請求的Servlet資源,並且還可以截獲響應,修改之后再發送給用戶。而Servlet監聽器可以 監聽客戶端發出的請求、服務器端的操作,通過監聽器,可以自動激發一些操作,如監聽在線人數。
Servlet過濾器簡介紹
Servlet過濾器是在Java Servlet 2.3 規范中定義的,它是一種可以插入的Web組件,它能夠對Servlet 容器的接收到的客戶端請求和向客戶端發出的響應對象進行截獲,過濾器支持對Servlet程序和JSP頁面的基本請求處理功能,如日志、性能、安全、會話 處理、XSLT轉換等。
Servlet過濾器本身不產生請求和響應,它只提供過濾作用,Servlet過濾器能夠在Servlet程序(JSP頁面)被調用之前檢查 request對象,修改請求頭和請求內容,在Servlet程序(JSP頁面)被調用之后,檢查response對象,修改響應頭和響應內容。
Servlet過濾器的特點
1.Servlet過濾器可以檢查和修改request和response對象。
2.Servlet過濾器可以被指定與特定的URL關聯,只有當客戶請求訪問該特定的URL時,才會觸發過濾器。
3.Servlet過濾器可以被串聯成串,形成過濾鏈,協同修改請求和響應。
Servlet過濾器的作用如下:
1.查詢請求並作出相應的行動。
2.阻塞請求--響應對,使其不能進一步傳遞。
3.修改請求頭和內容,用戶可以提供自定義的請求。
4.修改響應頭和內容,用戶可以通過提供定制的響應版本實現。
5.與外部資源進行交互。
2 Servlet過濾器的體系結構
Servlet過濾器用於攔截傳入的請求和傳出的響應,並監視、修改或以某種方式處理 正在通過的數據流。Servlet過濾器是自包含、模塊化的組件,可以將它們添加到請求/響應過濾鏈中,或者在不影響應用程序中其它Web組件的情況下刪 除它們。Servlet過濾器只在改動請求和響應的運行時處理,因而不應該將它們之間嵌入到Web應用程序框架,除非是通過Servlet API中良好定義的標准接口來實現。
Web資源可以配置成為沒有過濾器與之關聯(默認情況)、與單個過濾器關聯(典型情況),甚至是與一個過濾器鏈關聯。其功能與Servlet一 樣,主要是接收請求和響應對象,然后過濾器會檢查請求對象,並決定是將該請求轉發給鏈中的下一個過濾器,還是終止該請求並直接向客戶端發會一個響應,如果 請求被轉發了,它將被傳遞給過濾鏈中的下一個過濾器,或者Servlet程序(JSP頁面),在這個請求通過過濾器鏈並被服務器處理后,一個響應將以相反 的順序通過該過濾鏈發送回去,這樣就給每個Servlet過濾器提供了根據需要處理響應對象的機會。
當過濾器在Servlet 2.3規范中首次引入時,只能過濾客戶端和客戶端所訪問的指定Web資源之間的內容(請求/響應),如果該Web資源將請求轉發給其它Web資源時,那就 不能向幕后委托的任何請求應用過濾器。Servlet 2.4 規范消除了這個限制,Servlet過濾器現在可以應用於J2EE Web環境中存在請求和響應的任何地方。可見,Servlet過濾器可以應用在客戶端和Servlet程序之間、Servlet程序和Servlet程序 之間、Servlet程序和JSP頁面之間、JSP頁面和JSP頁面之間,具有強大的能力和靈活性。
2.1 Servlet過濾器對請求的過濾
Servlet過濾器對請求的過濾過程如下:
1.Servlet容器創建一個Servlet過濾器實例。
2.Servlet過濾器實例調用init()方法得到初始化參數。
3.Servlet過濾器實例調用doFilter()方法,根據初始化參數的值判斷該請求是否合法,如果該請求不合法,則阻塞該請求,如果是合法請求,則調用chain.doFilter(request,response)方法將該請求向后轉發。
13.2.2 Servlet過濾器對響應的過濾
Servlet過濾器對響應的過濾過程如下:
1.過濾器截獲客戶端的請求。
2.重新封裝ServletResponse,在封裝后的ServletResponse中提供客戶端自定義的輸出流。
3.將請求向后轉發。
4.Web組件產生響應。
5.過濾器從被封裝的ServletResponse中獲取客戶自定義的輸出流。
6.將響應內容通過客戶自定義的輸出流寫入緩沖流。
7.在緩沖流中修改響應內容后清空緩沖流,輸出響應內容。
2.3 Servlet過濾器的發布
Seevlet過濾器設計完畢之后,必須對該過濾器進行發布(配置), 發布一個Servlet過濾器時,必須在項目的web.xml文件中加入<filter>元素和<filter- mapping>元素,<filter>元素用來定義一個過濾器,該元素的屬性有:
屬性 |
描述 |
filter-name |
指定過濾器的名字 |
filter-class |
指定過濾器類 |
init-param |
指定過濾器的初始化參數 |
<filter-mapping>元素用於將過濾器與URL關聯,其屬性有:
屬性 |
描述 |
filter-name |
指定過濾器的名字 |
url-pattern |
指定與過濾器關聯的URL |
3 實現一個Servlet過濾器
3.1 Servlet過濾器接口的構成
所有的Servlet過濾器都必須實現javax.servlet.filter接口,該接口中定義了3個過濾器必須實現的方法:
1.void init(FilterConfig):過濾器的初始化方法,Servlet容器在創建過濾器實例時調用這個方法,在這個方法中可以讀出在web.xml文件中為該過濾器配置的初始化參數。
2.voiddoFilter(ServletRequest,ServletResponse,FilterChain):用於完成實際的過濾操作,當客戶請求訪問與過濾器相關聯的URL時,Servlet容器將先調用過濾器的這個方法,FilterChain參數用於訪問后續過濾器。
3.voiddestroy():過濾器在被取消前執行這個方法,釋放過濾器申請的資源。
3.2 Servlet過濾器的創建步驟
創建一個Servlet過濾器需要下面的步驟:
1.創建一個實現了javax.servlet.Filter接口的類。
2.重寫init(FilterConfig)方法,讀入為過濾器配置的初始化參數,申請過濾器需要的資源。
3.重寫方法doFilter(ServletRequest,ServletResponse,FilterChain),完成過濾操作,可以 從ServletRequest參數中得到全部的請求信息,從ServletResponse參數中得到全部的響應信息。
4.在doFilter()方法的最后,使用FilterChain參數的doFilter()方法將請求和響應后傳。
5.對響應的Servlet程序和JSP頁面注冊過濾器,在部署描述文件(web.xml)中使用<filter-apping>和<filter>元素對過濾器進行配置。
3.3 編寫過濾器類
在過濾器中,需要使用3個簡單的接口,它們是:分別是Filter、FilterChain、FilterConfig,全部包含在javax.servlet包中。從編程的角度看,過濾器類要實現Filter接口,然后使用實現了FilterChain和FilterConfig接口的對象來工作,FilterChain對象負責將請求和響應后傳,FilterConfig對象負責為過濾器讀初始化參數。
為了與過濾器的三步模式(創建、工作、撤消)保持一致,過濾器必須重寫Filter接口中的三個方法:
init():在容器實例化過濾器市時被調用,主要為過濾器做初始化,該方法有一個FilterConfig類型的形參。
doFilter():這個方法用來完成真正的過濾操作,它有3個形式參數:ServletRequest參數包含請求信息,ServletResponse參數包含響應信息,FilterChain參數用來將請求和響應向后傳遞。
destroy():過濾器被撤消時調用這個方法,釋放過濾器所 占有的資源。
在下面的例子中實現了一個簡單的Servlet過濾器(SessionFilter.java),它實現的功能是判斷客戶是否成功登錄,如果成功登錄,轉向正確頁面,否則返回一個錯誤頁面,提示客戶應該進行登錄。該過濾器代碼如下:
//includeList:數組,受保護的資源。
//logonList:數組,登錄頁面。
package ch13;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
public class SessionFilter implements Filter{
String logonStrings,includeStrings,redirectPath,disabletestfilter;
String[] logonList,includeList;
private boolean isContains(String containers,String[] regx) {
boolean result=false;
for(int i=0;i<regx.length;i++) {
if (containers.indexOf(regx[i])!=-1)
return true;
}
return result;
}
public FilterConfig config;
private void setFilterConfig(FilterConfig config) {
this.config=config;
}
private FilterConfig getFilterConfig(){
return config;
}
//必須重寫
public void init(FilterConfig filterConfig) throws ServletException{
this.config=filterConfig;
logonStrings=config.getInitParameter("logonStrings");
includeStrings=config.getInitParameter("includeStrings");
redirectPath=config.getInitParameter("redirectPath");
disabletestfilter=config.getInitParameter("disabletestfilter");
logonList=logonStrings.split(";");//分割為數組
includeList=includeStrings.split(";");//分割為數組
}
//必須重寫
public void doFilter(ServletRequest request,ServletResponse response,FilterChain
chain) throws ServletException, IOException {
HttpServletRequest httpreq=(HttpServletRequest)request;
HttpServletResponse httpres=(HttpServletResponse)response;
HttpServletResponseWrapper wrapper=new HttpServletResponseWrapper(
(HttpServletResponse)response);
if (disabletestfilter.toUpperCase().equals("Y")){
chain.doFilter(request,response);//如果不過濾
return;
}
Object user=httpreq.getSession().getAttribute("userinfo");
if (user==null){//該用戶沒有登錄
if (!isContains(httpreq.getRequestURI(),includeList)){
chain.doFilter(request,response);
return;//訪問的是不受保護的頁面,可以
}
if (isContains(httpreq.getRequestURI(),logonList)){
chain.doFilter(request,response);
return; //訪問的是登錄頁面,可以
}
wrapper.sendRedirect(redirectPath); //轉向登頁面
}else {//該用戶已經登錄
chain.doFilter(request,response);
}
}
//必須重寫
public void destroy() {
config=null;
}
}
在上面的這個Servlet過濾器程序中,根據用戶session對象中有無userinfo這個屬性來確定該用戶是否已經登錄。
3.4 配置部署過濾器
在WEB-INF/web.xml文件中用以下代碼配置過濾器:
<filter>
<filter-name>SessionFilter</filter-name>
<filter-class>ch13.SessionFilter</filter-class>
<init-param>
<param-name>logonStrings</param-name>
<param-value>Login.jsp</param-value>
</init-param>
<init-param>
<param-name>includeStrings</param-name>
<param-value>.jsp;.html;.htm</param-value>
</init-param>
<init-param>
<param-name>redirectPath</param-name>
<param-value>./Login.jsp</param-value>
</init-param>
<init-param>
<param-name>disabletestfilter</param-name>
<param-value>n</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>SessionFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
在上面的配置中,參數logonStrings指定可以訪問的登錄頁面,參數includeStrings指定受保護的資源的后綴,參數redirectPath表示沒有登錄時轉向的登錄頁面,參數disabletestfilter表示過濾器是否有效。而 /* 表示過濾器與所有的URL都關聯(對所有的訪問請求都進行過濾)。在瀏覽器中訪問任意的資源時,都要通過這個過濾器的過濾。
4 過濾器的應用案例.4.1 版權過濾器的應用案例
在一個Web應用中的所有頁面的下面添加上版權信息,通常的做法是采用<%@ include>指令或<c:import> 標簽,使用過濾器也是一個好辦法。
1.編寫過濾器類CopyrightFilter.java
package ch13;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
public class CopyrightFilter implements Filter{
private String date;
public FilterConfig config;
//必須重寫
public void init(FilterConfig filterConfig) throws ServletException{
this.config=filterConfig;
date=config.getInitParameter("date");
}
//必須重寫
public void doFilter(ServletRequest request,ServletResponse response,FilterChain
chain) throws ServletException, IOException {
chain.doFilter(request,response);
PrintWriter out=response.getWriter();
out.print("<br><center><font size='3' color='red'>版權所有:北京工業大學
</center></font>");
if (date!=null)
out.print("<br><center><font color='blue'>"+date+"</center></font>");
out.flush();
}
//必須重寫
public void destroy() {
config=null;
}
}
在這個過濾器中,在doFilter()方法的最后,通過response對象得到一個輸出流out,然后通過輸出流向客戶端輸出版權信息,這樣,每個頁面的最后都會出現過濾器添加的版權信息。
2.修改web.xml,配置該過濾器
<filter>
<filter-name>CopyrightFilter</filter-name>
<filter-class>ch13.CopyrightFilter</filter-class>
<init-param>
<param-name>date</param-name>
<param-value>2010-9</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CopyrightFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
3.測試
在瀏覽器中任意訪問一個頁面,都可以在看到在頁面的下部出現過濾器添加的版權信息。
4.2 禁止未授權的IP訪問站點過濾器的應用案例
使用過濾器禁止未授權的IP訪問站點是過濾器常見的應用,本例演示了如何利用過濾器實現禁止未授權的IP訪問站點。
1.編寫過濾器類FilterIP.java
package ch13;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
public class FilterIP implements Filter{
private String filterIP,error;
public FilterConfig config;
//必須重寫
public void init(FilterConfig filterConfig) throws ServletException{
this.config=filterConfig;
filterIP=config.getInitParameter("FilterIP");
if (filterIP==null) filterIP="";
error=config.getInitParameter("ERROR");
if (error==null) error="error.jsp";
}
//必須重寫
public void doFilter(ServletRequest request,ServletResponse response,FilterChain
chain) throws ServletException, IOException {
RequestDispatcher dispatcher=request.getRequestDispatcher("ErrorInfo.jsp");
String remoteIP=request.getRemoteAddr();//得到客戶的IP地址
if (remoteIP.equals(filterIP)) {
dispatcher.forward(request,response);
return;
} else
chain.doFilter(request,response);
}
//必須重寫
public void destroy() {
config=null;
}
}
在這個過濾器中,在doFilter()方法內,通過request對象得到客戶端的IP地址,如果客戶端的IP是被禁止的IP,則使用request對象將請求轉發給一個出錯頁面。
2.修改web.xml,配置過濾器
<filter>
<filter-name>FilterIP</filter-name>
<filter-class>ch13.FilterIP</filter-class>
<init-param>
<param-name>FilterIP</param-name>
<param-value>192.168.1.1</param-value>
</init-param>
<init-param>
<param-name>ERROR</param-name>
<param-value>error.jsp</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>FilterIP</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
對來自192.168.1.1的客戶的所有請求(/*)都進行過濾,轉移到error.jsp頁面。
3.編寫出錯頁面error.jsp
<%@ page contentType="text/html;charset=gb2312" %>
網站不允許IP地址為192.168.1.1的計算機訪問。
在IP地址為 192.168.1.1 的計算機上訪問網站的任何一個資源,都會轉移到error.jsp頁面。
4.3 過濾頁面內容(響應內容)
本過濾器使用HttpServletResponseWrapper類 來實現頁面內容的過濾,它的原理是讓Web資源先將頁面內容(響應內容)寫入到HttpServletResponseWrapper對象中,然后再在過 濾器中處理HttpServletResponseWrapper對象中的頁面內容(響應內容),最后再將處理好的頁面內容(響應內容)發送給客戶。
1.編寫HttpServletResponseWrapper類的子類.java
package ch13;
import java.io.ByteArrayOutputStream;
import java.io.OutputStreamWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import javax.servlet.ServletOutputStream;
public class WrapperResponse extends HttpServletResponseWrapper {
public static final int OT_NONE = 0, OT_WRITER = 1, OT_STREAM = 2;
private int outputType = OT_NONE;
private ServletOutputStream output = null;
private PrintWriter writer = null;
private ByteArrayOutputStream buffer = null;
//構造函數
public WrapperResponse(HttpServletResponse resp) throws IOException {
super(resp);
buffer = new ByteArrayOutputStream();
}
//得到字符輸出流
public PrintWriter getWriter() throws IOException {
if (outputType == OT_STREAM)
throw new IllegalStateException(); //已經用了OutputStream流
else if (outputType == OT_WRITER)
return writer;
else {
outputType = OT_WRITER;
writer = new PrintWriter(new OutputStreamWriter(buffer, getCharacterEncoding()));
return writer;
}
}
//得到字節輸出流
public ServletOutputStream getOutputStream() throws IOException {
if (outputType == OT_WRITER)
throw new IllegalStateException(); //已經用了Writer流
else if (outputType == OT_STREAM)
return output;
else {
outputType = OT_STREAM;
output = new WrappedOutputStream(buffer);
return output;
}
}
//刷新輸出內容
public void flushBuffer() throws IOException {
if (outputType == OT_WRITER)
writer.flush();
if (outputType == OT_STREAM)
output.flush();
}
//輸出緩沖區復位
public void reset() {
outputType = OT_NONE;
buffer.reset();
}
public String getResponseData() throws IOException {
flushBuffer();
return new String(buffer.toByteArray());
}
//內部類,將數據寫入自己的定義的緩沖區
class WrappedOutputStream extends ServletOutputStream {
private ByteArrayOutputStream buffer;
public WrappedOutputStream(ByteArrayOutputStream buffer) {
this.buffer = buffer;
}
public void write(int b) throws IOException {
buffer.write(b);
}
public byte[] toByteArray() {
return buffer.toByteArray();
}
}
}
在這個類中,一定要重寫response對象的關於輸出流(outputStream、writer)操作的方法:getOutputStream()、getWriter()、flushBuffer()、reset()。
2.編寫過濾器GavinFilter.java
package ch13;
import java.io.IOException;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class GavinFilter implements Filter {
private String oldword="%" , newword="百分號";
public void destroy(){}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain
chain) throws IOException, ServletException {
HttpServletResponse oldresponse = (HttpServletResponse)response;
WrapperResponse wrapperResponse = new WrapperResponse(oldresponse);
chain.doFilter(request, wrapperResponse); //讓服務器將響應內容寫到Wrapper中
String html = wrapperResponse.getResponseData(); //取出響應內容
oldresponse.getWriter().print(html.replaceAll(oldword, newword)); //替換頁面中的文字,然后發送給客戶
}
public void init(FilterConfig config) throws ServletException {
oldword=config.getInitParameter("oldword");
newword=config.getInitParameter("newword");
}
}
該過濾器將頁面內容(響應內容)中的字符 % 替換為百分號三個漢字,由此可見,實現了對響應內容的過濾。
3.對該過濾器的配置
<filter>
<filter-name>gavinFilter</filter-name>
<filter-class>ch13.GavinFilter</filter-class>
<init-param>
<param-name>oldword</param-name>
<param-value>%</param-value>
</init-param>
<init-param>
<param-name>newword</param-name>
<param-value>百分號</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>gavinFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
5 Servlet監聽器
Servlet監聽器也叫做 listener,通過它可以監聽Web應用的上下文(環境)信息、Servlet請求信息、Servlet會話信息,並自動根據不同情況,在后台調用相 應的處理程序。通過監聽器,可以自動激發一些操作,比如監聽在線人數,當增加一個HttpSession時就激發 sessionCreated(HttpSessionEvent)方法,這樣就可以給在線人數加1。
13.5.1 監聽器的原理
Servlet監聽器是Web應用開發的一個重要組成部 分,Servlet監聽器是在Servlet2.3規范中和Servlet過濾器一起引入的。在 Servlet2.4 規范中對其進行了比較大的改進。主要就是用來對Web應用進行監督和控制,極大地增強了Web應用的事件處理能力。
Servlet監聽器的功能比較類似於Java中的GUI程序的監聽器,可以監聽由於Web應用中的狀態改變而引起的Servlet容器產生的相應事件,然后接收並處理這些事件。
5.2 監聽器的類型
在 Servlet 2.4 規范中,根據監聽對象的類型和范圍,將監聽器分為3類:ServletRequest監聽器(請求監聽器)、HttpSession監聽器(會話監聽器)、ServletContext監聽器(上下文監聽器),其中請求監聽器(ServletRequest監聽器)是 Servlet 2.4 規范中新增加的監聽器,可以用來監聽客戶的端請求,在Servlet 2.4 規范中包含了8個監聽器接口和6個監聽器事件類,具體的監聽器接口和事件如下表:
監聽對象 |
監聽接口 |
監聽事件 |
ServletRequest |
ServletRequestListener (2個方法) |
ServletRequestEvent |
ServletRequestAttributeListener (3個方法) |
ServletRequestAttributeEvent |
|
HttpSession |
HttpSessionListener (2個方法) |
HttpSessionEvent |
HttpSessionActivationListener (2個方法) |
||
HttpSessionAttributeListener (3個方法) |
HttpSessionBindingEvent |
|
HttpSessionBindingListener (2個方法) |
||
ServletContext |
ServletContextListener (2個方法) |
ServletContextEvent |
ServletContextAttributeListener (3個方法) |
ServletContextAttributeEvent |
1.被監聽對象ServletContext
對ServletContext對象(JSP頁面中稱為application對象)實現監聽涉及2個接口:
(1)ServletContextListener接口:用於監聽ServletContext對象的創建和刪除:接口中定義的回調方法有:
當創建一個ServletContext對象時,激發 contextInitialzed(ServletContextEvent)方法。
當撤消一個ServletContext對象時,激發 contextDestroyed(ServletContextEvent)方法。
(2)ServletContextAttributeListener接口:用於監聽ServletContext對象的屬性操作。接口中定義的回調方法有:
增加屬性時,激發 attributeAdded(ServletContextAttributeEvent)
刪除屬性時,激發 attributeRemoved(ServletContextAttributeEvent)
修改屬性時,激發 attributeReplaced(ServletContextAttributeEvent)
2.被監聽對象HttpSession
對HttpSession對象(session)實現監聽涉及4個接口:
(1)HttpSessionListener接口:這個接口監聽Http會話的創建和撤消,並在某個session對象建立和銷毀之前調用某個方法。接口中定義的回調方法有:
創建一個session對象時,激發 sessionCreated(HttpSessionEvent)
刪除一個session對象時,激發 sessionDestroyed(HttpSessionEvent)
(2)HttpSessionActivationListener接口:監聽Http會話的active和passivate狀態。接口中定義的回調方法有:
session對象被保存到磁盤時,激發 sessionWillPassivate(HttpSessionEvent)
session對象被調入內存時,激發 sessionDidActivate(HttpSessionEvent)
Activate與Passivate是用於置換session對象的動作,當Web服務器因為資源利用或負載平衡等原因要將內存中的 session對象暫時儲存至硬盤或其它儲存器時(通過對象序列化),所作的動作稱之為Passivate,而硬盤或儲存器上的session對象重新加 載到JVM中時所采的動作稱之為Activate。sessionDidActivate()方法與 sessionWillPassivate()方法分別於Activeate后與Passivate前被調用。
(3)HttpSessionAttributeListener接口:監聽Http會話中屬性的設置信息。接口中定義的回調方法有:
向某個session對象中增加新屬性時,激發 attributeAdded(HttpSessionBindingEvent)
刪除某個session對象中的屬性時,激發 attributeRemoved(HttpSessionBindingEvent)
修改某個session對象中的屬性時,激發 attributeReplaced(HttpSessionBindingEvent)
使用HttpSessionBindingEvent事件類對象的getSession()方法可以得到這個session對象,使用 HttpSessionBindingEvent對象的getName()方法得到屬性的名字,使用getValue()方法得到屬性的值。
若有屬性加入到某個會話(HttpSession)對象,則會調用attributeAdded(),同理在替換屬性與移除屬性時,會分別調用attributeReplaced()、attributeRemoved()。
(4)HttpSessionBindingListener接口:這是唯一一個不需要在web.xml中進行配置的監聽器接口,監聽Http會話中屬性的變化情況。接口中定義的回調方法有:
屬性被加入到session中時,激發屬性的 valueBound(HttpSessionBindingEvent)
屬性被從session中刪除時,激發屬性的 valueUnbound(HttpSessionBindingEvent)
使用HttpSessionBindingEvent事件類對象的getSession()方法可以得到這個session對象,使用 HttpSessionBindingEvent對象的getName()方法得到屬性的名字,使用getValue()方法得到屬性的值。
如果一個對象object實現了HttpSessionBindingListener接口時,當把object對象保存到session中時, 就會自動調用object對象的valueBound()方法,如果對象object被從session(HttpSession)移除時,則會調用 object對象的valueUnbound()方法。使用這個接口,可以讓一個對象自己知道它自己是被保存到了session中,還是從session 中被刪除了。
3.被監聽對象ServletRequest
對ServletRequest對象(request)實現監聽涉及2個接口:
(1)ServletRequestListener接口:監聽請求的創建和撤消,該接口用來監聽請求到達和結束,因此可以在請求達到前和請求結束前執行一些用戶行為。 接口中定義的回調方法有:
請求對象初始化時,激發 requestInitialized(ServletRequestEvent)
請求對象被撤消時,激發 requestDestroyed(ServletRequestEvent)
在request(HttpServletRequest)對象建立或被消滅時,會分別調用requestInitialized()和requestDestroyed()方法。
(2)ServletRequestAttributeListener接口:監聽請求中(request對象中)的屬性變化。接口中定義的回調方法有:
向某個request對象中增加屬性時被調用attributeAdded(ServletRequestAttributeEvent)方法。
從某個request對象中刪除屬性時被調用attributeRemoved(ServletRequestAttributeEvent)方法。
修改某個request中的屬性時被調用attributeReplaced(ServletRequestAttributeEvent)方法。
使用ServletRequestEvent類的getServletRequest()方法可以得到這個被監聽的請求對象,使用 ServletRequestAttributeEvent類的getName()方法可以得到屬性名,getValue()方法可以得到屬性的值。
若有屬性加入到某個request對象中時則會調用attributeAdded(),同理在替換屬性與刪除屬性時,會分別調用attributeReplaced()、 attributeRemoved()。
當Web應用程序啟動后,在處理任何請求之前,調用contextInitialzed()方法和getInitParamter()方法,返回 在配置文件中為定義的環境初始化信息。不同的組件,如Servlet、JSP、監聽器和過濾器等,通過ServletRequest、 HttpSession 和 ServletContext達到數據共享,這些類都提供了下面的一組方法,可以使用這組方法來設置、獲取、刪除屬性:
public void setAttribute("屬性名",屬性值);
public Object getAttribute("屬性名");
public void removeAttribute("屬性名");
5.3 監聽器管理共享數據庫連接
在web.xml中,使用<listener>來配置監聽器,語法是:
<listener>
<listener-class>包名.類名</listener-class>
</listener>
比如:創建一個ServletContext對象監聽器,在一個Web項目一啟動就創建一個與數據庫的連接,保存在application對象中,這個連接一直保存到Web項目關閉時為止。程序代碼如下:
package ch13;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import java.sql.*;
//import ch7.db.*;
public final class MyConnectionManager implements ServletContextListener {
Connection con=null;
public void contextInitialized(ServletContextEvent e) {//重寫接口定義的方法,項目啟動是調用該方法
ConnectDB db=new ConnectDB();
con=db.getConnection(); //使用對象db創建數據庫連接
e.getServletContext().setAttribute("con",con);//與數據庫的連接保存入application對象中
}
public void contextDestroyed(ServletContextEvent e) {//重寫接口定義的方法,項目關閉時調用該方法
try {
con.close();
}
catch(Exception e1){}
}
}
在web.xml文件對這個ServletContext類型的監聽器進行配置:
<listener>
<listener-class>ch13.MyConnectionManager</listener-class>
</listener>
這個監聽器能保證每新創建一個ServletContext對象時(一個Web項目只有一個 ServletContext對象),該Web項目都會有一個可以使用的數據庫連接,並且這個數據庫連接會在該ServletContext對象關閉(結 束)的時候隨之關閉。
測試頁面testcon.jsp:
<%@ page contentType="text/html" pageEncoding="GB18030"%>
<br><%= "得到的數據庫連接:"+application.getAttribute("con") %>
<br><h1>請注意安裝數據庫的驅動程序</h1>
5.4 監聽器的應用案例
下面是一個在線用戶數量監聽器,這個監聽器可以實時統計在線人數,在 ServletContext初始化和撤消時,在服務器控制台打印出對應信息,當ServletContext對象里的屬性增加、修改、刪除時,在服務器 控制台打印相應的信息。要完成上面的監聽功能,需要使用3個接口:
HttpSessionListener:監督HttpSession對象的創建和撤消,統計人數。
ServletContextListener:監督ServletContext對象的創建和撤消。
ServletContextAttributeListener:監督ServletContext的屬性變化。
1.監聽器程序代碼OnLineCountListener.java
package ch13;
import javax.servlet.*;
import javax.servlet.http.*;
public final class OnLineCountListener implements HttpSessionListener,
ServletContextAttributeListener, ServletContextListener {
private int count;
private ServletContext context=null;
//構造函數
public OnLineCountListener() {
count=0;//人數
}
//重寫HttpSessionListener接口中的2個方法,完成對session對象創建和撤消的監視
public void sessionCreated(HttpSessionEvent se) {//創建了一個session對象
count++;//人數加1
setContext(se);
}
public void sessionDestroyed(HttpSessionEvent se){//撤消了一個session對象
count--;//人數減1
setContext(se);
}
private void setContext(HttpSessionEvent se){
se.getSession().getServletContext().setAttribute("onLine",new Integer(count));
}
//重寫ServletContextAttributeListener接口中的3個方法
public void attributeAdded(ServletContextAttributeEvent event) {//添加了屬性
log("attributeAdded("+event.getName()+","+event.getValue()+")");
}
public void attributeRemoved(ServletContextAttributeEvent event) {//刪除了屬性
log("attributeRemove("+event.getName()+","+event.getValue()+")");
}
public void attributeReplaced(ServletContextAttributeEvent event) {//替換了原有的屬性
log("attributeReplaced("+event.getName()+","+event.getValue()+")");
}
//重寫ServletContextListener接口中的2個方法
public void contextDestroyed(ServletContextEvent event) {//Web項目關閉
log("contextDestroyed()");
context=null;
}
public void contextInitialized(ServletContextEvent event) {//Web項目啟動
this.context=event.getServletContext();
log("contextInitialized()");
}
//顯示信息
private void log(String message){
System.out.println("ContextListener:"+message);
}
}
在OnLineCountListener類中,用count保存目前在線人數,每增加一個session對象,人數加1,每撤消一個session對象,人數減1。人數保存在ServletContext對象中,使得任何頁面都可以使用。
2.在web.xml文件中配置監聽器
<listener>
<listener-class>ch13.OnLineCountListener</listener-class>
</listener>
3.編寫測試頁面(2個)
listener.jsp------>exit.jsp
listener.jsp頁面內容
<%@ page contentType="text/html;charset=gb2312" %>
目前在線人數:<font color="red"><%=application.getAttribute("onLine")%></font><br>
退出會話:
<form action="exit.jsp" method="post">
<input type="submit" value="exit">
</form>
exit.jsp頁面內容
<%@ page contentType="text/html;charset=gb2312" %>
你已經退出會話<% session.invalidate(); %>
可以單獨啟動5個瀏覽器窗口,每個窗口代表一個客戶,因此在線人數是5。
5.5 HttpSessionBindingListener 接口的使用
設計一個學生對象Student,當將該學生對象存入 session中時,他的年齡增加10歲,當將這個學生對象從session中刪除時,他的年齡減少5歲。
學生類Student.java
package ch13;
import javax.servlet.*;
import javax.servlet.http.*;
public class Student implements HttpSessionBindingListener {
private int age=30;
public void valueBound(HttpSessionBindingEvent arg0) {//存入session時自動調用
age+=10;
}
public void valueUnbound(HttpSessionBindingEvent arg0) {//從session中刪除時自動調用
age-=5;
}
public int getAge() {return age;}
}
測試頁面bind.jsp
<%@ page contentType="text/html;charset=gb2312" import="ch13.Student"%>
<%
Student student=new Student();
out.println("學生年齡:"+student.getAge()+"<br>");
session.setAttribute("st",student);
out.println("存入session后,該學生年齡:"+student.getAge()+"<br>");
session.removeAttribute("st");
out.println("從session刪除,該學生年齡:"+student.getAge()+"<br>");
%>