一、監聽器的概覽
監聽器是指專門用于對其他對象身上發生的事件或狀態的改變進行監聽和相應處理的對象,當被監視的對象發生變化時,立即采取相應的行動。比如統計用戶在線人數,監聽人就是web應用服務器,被監聽者就是session對象。
??web監聽器是Servlet規范中定義的一種特殊類,用于監聽ServletContext、HttpSession和ServletRequest,可以在事件發生前、發生后做一些必要的處理。web監聽器可以用于統計用戶在線人數和在線用戶、系統啟動時加載初始化信息、統計網站訪問量等。
二、監聽器的分類
按監聽對象劃分
- 用于監聽應用程序環境(ServletContext)的事件監聽器
- 用于監聽用戶會話對象(HttpSession)的事件監聽器
- 用于監聽請求消息對象(ServletRequest)的事件監聽器
按監聽的事件劃分
- 監聽域對象自身的創建和銷毀的事件監聽器
- 監聽域對象中的屬性的增加和刪除的事件監聽器
- 監聽綁定到HttpSession域中的某個對象的狀態的事件監聽器
三、監聽器的啟動順序
四、監聽器詳解
監聽器在web容器中的配置方式
①web.xml中配置監聽器:
<web-app...>
...
<listener>
<listener-class>com.listener.testListener</listener-class>
</listener>
<context-param>
<param-name>initParam</param-name>
<param-value>zoyoto</param-value>
</context-param>
...
<web-app...>
Servelt容器先解析web.xml,獲取Listener的值,通過反射生成監聽器對象。
②在監聽器類上標注@WebListener
@WebListener()
public class SomeSessionListener implements HttpSessionListener{...}
下面我們來看一下不同類別監聽器的解析。
(1)監聽域對象自身的創建和銷毀的事件監聽器
- ServletContext→ServletContextListener
- HttpSession→HttpSessionListener
- ServletRequest→ServletRequestListener
注意:上面三種監聽器均要在web容器中注冊
a.ServletContextListener
??一個項目中只能定義一個ServletContext,但是一個ServletContext可以注冊多個ServletContextListener。ServletContextListener是對ServeltContext的一個監聽,在ServeltContext生成或銷毀后才被調用。在方法里面調用event.getServletContext()可以獲取ServletContext。
??ServeltContext是一個上下文對象,它的數據供所有的應用程序共享,所以ServletContextListener的主要用途就是:做定時器和全局屬性對象。下面我們看一下如何使用ServeltContext做定時器:
//ServletContextListener可以負責在應用服務器啟動時打開定時器
@WebListener()
public class taskListener implements ServletContextListener{
private java.util.Timer timer = null;
//ServletContext創建時調用
@Override
public void contextDestroyed(ServletContextEvent event){
timer = new java.util.Timer(true);
event.getServletContext().log("定時器已啟動");
//schedule(TimerTask task, long delay, long period)方法設定指定任務task在指定延遲delay后進行固定延遲peroid的執行
timer.schedule(new MyTask(event.getServletContext()), 0, 60*60*1000);
event.getServletContext().log("已經添加任務調度表");
}
//ServletContext銷毀時調用
@Override
public void contextInitialized(ServletContextEvent servletContextEvent){
timer.cancel();
event.getServletContext().log("定時器銷毀");
}
}
//定時任務
public class MyTask extends TimerTask{
private ServletContext context = null;
private static boolean isRunning = false;
private static final int STATISTICS_SCHEDULE_HOUR = 0;
public MyTask(ServletContext context){
this.context=context;
}
@Override
public void run(){
Calendar cal = Calendar.getInstance();
if (!isRunning) {
//查看是否為凌晨
if (STATISTICS_SCHEDULE_HOUR == cal.get(Calendar.HOUR_OF_DAY)){
isRunning = true;
context.log("開始執行指定任務");
//TODO自定義添加任務詳情
executeTask();
//指定任務執行結束
isRunning = false;
context.log("指定任務執行結束");
}
}else{
context.log("上一次任務執行還未結束");
}
}
}
b.HttpSessionListener
??一個HttpSession可以注冊多個HttpSessionListener。HttpSessionListener是對Session的監聽,在Session生成或銷毀后被調用。HttpSessionListener主要用途是統計在線人數和記錄訪問日志。下面我們來看一下如何用該監聽器統計在線人數:
@WebListener()
public class testListener implements HttpSessionListener{
private static Integer userNumber = 0;
public static int getCount(){
return userNumber;
}
//session創建時調用
@Override
public void sessionCreated(HttpSessionEvent httpSessionEvent){
System.out.println("sessionCreated");
userNumber++:
}
//session銷毀時調用
@Override
public void sessionDestroyed(HttpSessionEvent httpSessionEvent){
System.out.println("sessionDestroyed");
userNumber--:
}
}
啟動服務器,然后在瀏覽器輸入URL,就會發現控制臺輸出了sessionCreated;這時我們關掉瀏覽器,session并不會立刻就銷毀,而是隔一段時間才銷毀。我們可以在web.xml中設置session-timeout,假如session-timeout為0,就說明這個session沒有超時的限制。我們設置為1,意思就是等一分鐘,如果session沒有點擊的話,就會被銷毀,就會打印出sessionDestroyed。
<session-config>
<session-timeout>1</session-timeout>
</session-config>
在HttpSessionListener監聽器里面,我們可以用httpSessionEvent.getSession().getServletContext()
得到上下文,因為httpSessionEvent中只有getSession(),沒有getServletcontext()。
c.ServletRequestListener
??一個ServletRequest可以注冊多個ServletRequestListener。ServletRequestListener接口用于監聽Web應用程序中ServletRequest對象的創建和銷毀。ServletRequestListener主要用途是讀取參數和記錄訪問歷史。
@WebListener()
public class testListener implements ServletRequestListener{
//request創建時(發送請求)調用
@Override
public void requestInitialized(ServletRequestEvent servletRequestEvent){
System.out.println("requestCreated");
}
//request處理完畢銷毀時調用
@Override
public void requestDestroyed(ServletRequestEvent servletRequestEvent){
System.out.println("requestDestroyed");
}
}
此監聽器可以啟動Tomcat發送請求進行測試。
(2)監聽域對象中的屬性的增加和刪除的事件監聽器
- ServletContext→ServletContextAttributeListener
- HttpSession→HttpSessionAttributeListener
- ServletRequest→ServletRequestAttributeListener
注意:上面三種監聽器均要在web容器中注冊
a.ServletContextAttributeListener
??此接口用于監聽ServletContext屬性的變化。
@WebListener()
public class testListener implements ServletContextAttributeListener{
//添加屬性
@Override
public void attributeAdded(ServletContextAttributeEvent e){
System.out.println("attributeAdded"+e.getName());
}
//刪除屬性
@Override
public void attributeRemoved(ServletContextAttributeEvent e){
System.out.println("attributeRemoved"+e.getName());
}
//修改屬性
@Override
public void attributeReplaced(ServletContextAttributeEvent e){
System.out.println("attributeReplaced"+e.getName());
}
}
b.HttpSessionAttributeListener
??此接口用于監聽Session屬性的變化。
@WebListener()
public class testListener implements HttpSessionAttributeListener{
//session.setAttribute()
@Override
public void attributeAdded(HttpSessionBindingEvent e){
System.out.println("attributeAdded"+e.getName());
}
//session.removeAttribute()
@Override
public void attributeRemoved(HttpSessionBindingEvent e){
System.out.println("attributeRemoved"+e.getName());
}
//session.replaceAttribute()
@Override
public void attributeReplaced(HttpSessionBindingEvent e){
System.out.println("attributeReplaced"+e.getName());
}
}
c.ServletRequestAttributeListener
??此接口用于監聽request屬性的變化。
@WebListener()
public class testListener implements ServletRequestAttributeListener{
//request.setAttribute()
@Override
public void attributeAdded(HttpSessionBindingEvent e){
System.out.println("attributeAdded"+e.getName());
}
//request.removeAttribute();
@Override
public void attributeRemoved(HttpSessionBindingEvent e){
System.out.println("attributeRemoved"+e.getName());
}
//對已經存在于request中的屬性再次調用request.setAttribute("user", "bbb")
//如request.setAttribute("user", "aaa");request.setAttribute("user", "bbb");
@Override
public void attributeReplaced(HttpSessionBindingEvent e){
System.out.println("attributeReplaced"+e.getName());
}
}
(3)監聽綁定到HttpSession域中的某個對象的狀態的事件監聽器
HttpSession中的對象狀態:①綁定→解除綁定??②鈍化→活化
3.1鈍化與活化
鈍化:將session對象持久化到存儲設備上;
活化:將session從存儲設備上恢復
Session正常是放到服務器內存當中的,服務器會對每一個在線用戶創建一個Session對象,如果當前用戶很多,Session內存的開銷是非常大的,影響性能。而Session的鈍化機制可以解決這個問題。
??鈍化的本質就是把一部分比較長時間沒有變動的Session暫時序列化到系統文件或者數據庫系統當中,等該Session的用戶重新使用的時候,服務器在內存中找不到Session,就會到磁盤中去找,然后把Session反序列化到內存中。整個過程由服務器自動完成。
??注意,Session只有可以被序列化才能被鈍化。Session序列化時,服務器會把Session保存到硬盤中,以sessionID命名,以“.session”作為擴展名。
??Session鈍化機制由SessionManager管理,Tomcat有兩種Session管理器:
①StandardManager(標準會話管理器)
<Manager className="org.apache.catalina.session.StandardManager"
maxInactiveInterval="7200"/>
- Tomcat6的默認會話管理器,用于非集群環境中對單個處于運行狀態的Tomcat實例會話進行管理。
- 當運行Tomcat時,StandardManager實例負責在內存中管理Session;但當服務器關閉時,會將當前內存中的Session寫入到磁盤上的一個名叫SESSION.ser的文件,等服務器再次啟動時,會重新載入這些Session。
- 另一種情況是Web應用程序被重新加載時,內存中的Session對象也會被鈍化到服務器的文件系統中。
- 鈍化后的文件默認被保存在Tomcat的安裝路徑
$CATALINA_HOME/work/Catalina/<hostname>/<webapp-name>/
下的SESSIONS.ser
文件中。
②PersistentManager(持久會話管理器)
- PersistentManager和StandardManager的區別在于PersistentManager自己實現了Store類,使Session可以被保存到不同地方(Database,Redis等),而不局限于只保存在SESSION.ser文件中。Store表示了管理session對的二級存儲設備。
<ManagerclassName="org.apache.catalina.session.PersistentManager"
debug="0"
<!--當Tomcat正常停止及重啟動時,是否要儲存及重載會話。-->
saveOnRestart="true"
<!--可容許現行最大會話的最大數,-1代表無限制-->
maxActiveSession="-1"
<!--在調換會話至磁盤之前,此會話必須閑置的最小時間-->
minIdleSwap="0"
<!--在文件交換之前,此會話可以閑置的最大時間(以秒為單位)。-1表示會話不應該被強迫調換至文件。-->
maxIdleSwap="0"
<!--在備份之前,此會話必須閑置的最大時間。-1表示不進行備份-->
maxIdleBackup="-1"
<!--保存在Reids中-->
<Store className="com.cdai.test.RedisStore" host="192.168.1.1"port="6379"/>
</Manager>
- PersistentManager支持兩種鈍化驅動類:org.apache.Catalina.FileStore和org.apache.Catalina.JDBCStore,分別支持將會話保存至文件存儲(FileStore)或JDBC存儲(JDBCStore)中。
<!--保存到文件中-->
<Manager className="org.apache.catalina.session.PersistentManager"
saveOnRestart="true">
<!--每個用戶的會話會被保存至directory指定的目錄中的文件中-->
<Store className="org.apache.catalina.session.FileStore"
directory="/data/tomcat-sessions"/>
</Manager>
<!--保存到JDBCStore中-->
<Manager className="org.apache.catalina.session.PersistentManager"
saveOnRestart="true">
<Store className="org.apache.catalina.session.JDBCStore"
driverName="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/mydb?user=root;password=123"/>
</Manager>
- 在持久化管理器中,session可以被備份或換出到存儲器中。
- 換出:當內存中有過多的session對象時,持久化管理器會直接將session對象換出,直到當前活動對象等于maxActiveSession指定的值。
- 備份:不是所有的session都會備份,PersistentManager只會將那些空閑時間超過maxIdleBackup的session備份到文件或數據庫中。該任務由processMaxIdleBackups方法完成。
- 此時Tomcat只是在下面三種情況會將Session通過Store保存起來。
- 當Session的空閑時間超過minIdleSwap和maxIdleSwap時,會將Session換出
- 當Session的空閑時間超過maxIdleBackup時,會將Session備份出去
- 當Session總數大于maxActiveSession時,會將超出部分的空閑Session換出
3.2接口規范
??servlet規范中提供的兩種接口用以監聽session內的對象:
HttpSessionBindingListener HttpSessionActivationListener
| |
/ \ / \
valueBound valueUnBound sessionWillPassivate sessionDidActivate
(綁定) (解除綁定) (鈍化) (活化)
以上接口皆不需要在web容器中注冊。
(1)綁定和解綁接口
??當對象被放到session里執行或從session里移除就會執行HttpSessionBindingListener內的方法,但是對象必須實現該Listener接口。需要注意的是,我們創建的這個類并不是創建一個監聽器,而是創建一個被監聽器綁定的Javabean。
public class User implements HttpSessionBindingListener{
private String userName;
private String password;
//對象被放進session時觸發,如session.setAttribute("user",user);
@Override
public void valueBound(HttpSessionBindingEvent e){
//getName()方法可以取得屬性設定或移除時指定的名稱
System.out.println(this + "被綁定到session \"" + e.getSession.getId() + "\"的" +e.getName()+ "屬性上);
}
//從session移除后觸發,
@Override
public void valueUnBound(HttpSessionBindingEvent e){
System.out.println(this + "被從session \"" + e.getSession.getId() + "\"的" +e.getName()+ "屬性上移除);
}
//此處省略getter和setter
}
valueUnbound方法將被以下任一條件觸發:
a. 執行session.setAttribute("uocl", 非uocl對象) 時。
b. 執行session.removeAttribute("uocl") 時。
c. 執行session.invalidate()時。
d. session超時后。
(2)鈍化和活化接口
??服務器內存對session進行鈍化或者活化時你會收到監聽事件。什么時候序列化和反序列化完全由容器決定,我們只能用HttpSessionActivationListener接口監聽器監聽對象是否被鈍化。
//要注意只有實現Serializable接口才能被鈍化
public class User implements HttpSessionActivationListener,Serializable{
private String userName;
private String password;
//被鈍化時調用
@Override
public void sessionWillPassivate(HttpSessionEvent e){
System.out.println(this + "即將保存到硬盤。sessionId: " + e.getSession.getId());
}
//被活化時調用
@Override
public void sessionDidActivate(HttpSessionEvent e){
System.out.println(this + "已經成功從硬盤中加載。sessionId: " + e.getSession.getId());
}
//此處省略getter和setter
}
測試的時候先把Tomcat關掉,就會發現控制臺輸出了
sessionWillPassivate org.apache.catalina.session.StandardSessionFacade@4f2d26d2
也就是session已經被鈍化了,此時在Tomcat安裝路徑下會發現SESSION.er文件。然后我們再來重啟Tomcat,發現控制臺輸出:
sessionDidActivate org.apache.catalina.session.StandardSessionFacade@4f2d26d2
此時會發現在Tomcat安裝路徑下的SESSION.er文件消失了,也就說明session已經被活化了。
??同樣的,如果我們設定了maxIdleeSwap="1",當用戶開著瀏覽器一分鐘不操作頁面的話服務器就會將session鈍化,將session生成文件放在tomcat工作目錄下,直到再次訪問才會被激活。