本文包括:
1、Listener簡(jiǎn)介
2、Servlet監(jiān)聽器
3、監(jiān)聽三個(gè)域?qū)ο髣?chuàng)建和銷毀的事件監(jiān)聽器
4、監(jiān)聽三個(gè)域?qū)ο蟮膶傩?Attribute)的變化的事件監(jiān)聽器
5、監(jiān)聽綁定到 HttpSession 域中的某個(gè)對(duì)象的狀態(tài)的事件監(jiān)聽器
1、Listener簡(jiǎn)介
-
Listener(監(jiān)聽器)就是一個(gè)實(shí)現(xiàn)特定接口的普通java程序,這個(gè)程序?qū)iT用于監(jiān)聽另一個(gè)java對(duì)象的方法調(diào)用或?qū)傩愿淖?,?dāng)被監(jiān)聽對(duì)象發(fā)生上述事件后,監(jiān)聽器某個(gè)方法將立即被執(zhí)行。
-
為了加深理解,自定義監(jiān)聽器來(lái)練練手,假設(shè)現(xiàn)在有個(gè)體重100的人要吃飯了,要監(jiān)聽他吃飯的動(dòng)作,捕捉到了之后再打印它的體重,具體思路如下;
-
事件源類:
public class Person { private String name; private int weight;// 體重 public String getName() { return name; } public void setName(String name) { this.name = name; } public int getWeight() { return weight; } public void setWeight(int weight) { this.weight = weight; } } -
監(jiān)聽器接口:
public interface PersonListener { public void personeating(PersonEvent event);// 監(jiān)聽方法,需要一個(gè)事件對(duì)象作為參數(shù) } -
事件類:
public class PersonEvent { private Object source;// 事件源 public Object getSource() { return source; } public void setSource(Object source) { this.source = source; } // 提供一個(gè)這樣的構(gòu)造方法:構(gòu)造事件對(duì)象時(shí),接收事件源(Person) public PersonEvent(Person person) { this.source = person; } } -
在事件源中注冊(cè)監(jiān)聽器:
public class Person { private String name; private int weight;// 體重 private PersonListener listener; // 注冊(cè)監(jiān)聽器 public void addPersonListener(PersonListener listener) { this.listener = listener; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getWeight() { return weight; } public void setWeight(int weight) { this.weight = weight; } } -
操作事件源 ----- 在事件源方法中,構(gòu)造事件對(duì)象,參數(shù)為當(dāng)前事件源(this),傳遞事件對(duì)象給監(jiān)聽器的監(jiān)聽方法:
public class Person { private String name; private int weight;// 體重 private PersonListener listener; // 注冊(cè)監(jiān)聽器 public void addPersonListener(PersonListener listener) { this.listener = listener; } // 吃飯 public void eat() { // 體重增加 weight += 5; // 調(diào)用監(jiān)聽器監(jiān)聽方法 if (listener != null) { // 監(jiān)聽器存在 // 創(chuàng)建事件對(duì)象 --- 通過(guò)事件對(duì)象可以獲得事件源 PersonEvent event = new PersonEvent(this); listener.personeating(event); } } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getWeight() { return weight; } public void setWeight(int weight) { this.weight = weight; } } -
測(cè)試:
public class PersonTest { public static void main(String[] args) { // 步驟一 創(chuàng)建事件源 Person person = new Person(); person.setName("小明"); person.setWeight(100); // 步驟二 給事件源注冊(cè)監(jiān)聽器(該監(jiān)聽器由匿名內(nèi)部類創(chuàng)建) person.addPersonListener(new PersonListener() { @Override public void personeating(PersonEvent event) { System.out.println("監(jiān)聽到了,人正在吃飯!"); // 在監(jiān)聽方法中可以獲得事件源對(duì)象,進(jìn)而可以操作事件源對(duì)象 Person person = (Person) event.getSource(); System.out.println(person.getName()); System.out.println(person.getWeight()); } }); // 步驟三 操作事件源 person.eat();// 結(jié)果監(jiān)聽方法被調(diào)用 } }
-
2、Servlet監(jiān)聽器
-
在Servlet規(guī)范中定義了多種類型的監(jiān)聽器,它們用于監(jiān)聽的事件源是三個(gè)域?qū)ο螅謩e為:
ServletContext
HttpSession
ServletRequest
-
Servlet規(guī)范針對(duì)這三個(gè)域?qū)ο笊系牟僮?,又把這多種類型的監(jiān)聽器劃分為三種類型:
監(jiān)聽三個(gè)域?qū)ο蟮膭?chuàng)建和銷毀的事件監(jiān)聽器
監(jiān)聽三個(gè)域?qū)ο蟮膶傩?Attribute)的增加和刪除的事件監(jiān)聽器
監(jiān)聽綁定到 HttpSession 域中的某個(gè)對(duì)象的狀態(tài)的事件監(jiān)聽器。(查看API文檔)
-
編寫 Servlet 監(jiān)聽器:
和編寫其它事件監(jiān)聽器一樣,編寫Servlet監(jiān)聽器也需要實(shí)現(xiàn)一個(gè)特定的接口,并針對(duì)相應(yīng)動(dòng)作覆蓋接口中的相應(yīng)方法。
和其它事件監(jiān)聽器略有不同的是,Servlet監(jiān)聽器的注冊(cè)不是直接注冊(cè)在事件源上,而是由WEB容器負(fù)責(zé)注冊(cè),開發(fā)人員只需在web.xml文件中使用
<listener>標(biāo)簽配置好監(jiān)聽器,web容器就會(huì)自動(dòng)把監(jiān)聽器注冊(cè)到事件源中。-
一個(gè) web.xml 文件中可以配置多個(gè) Servlet 事件監(jiān)聽器,web 服務(wù)器按照它們?cè)?web.xml 文件中的注冊(cè)順序來(lái)加載和注冊(cè)這些 Serlvet 事件監(jiān)聽器。配置代碼如下所示:
<!-- 對(duì)監(jiān)聽器進(jìn)行注冊(cè) --> <!-- 監(jiān)聽器和Servlet、Filter不同,不需要url配置,監(jiān)聽器執(zhí)行不是由用戶訪問(wèn)的,監(jiān)聽器 是由事件源自動(dòng)調(diào)用的 --> <listener> <listener-class>cn.itcast.servlet.listener.MyServletContextListener</listener-class> </listener>
3、監(jiān)聽三個(gè)域?qū)ο髣?chuàng)建和銷毀的事件監(jiān)聽器
3.1、ServletContextListener
-
ServletContextListener 接口用于監(jiān)聽 ServletContext 對(duì)象的創(chuàng)建和銷毀事件。
-
當(dāng) ServletContext 對(duì)象被創(chuàng)建時(shí),調(diào)用接口中的方法:
ServletContextListener.contextInitialized (ServletContextEvent sce) -
當(dāng) ServletContext 對(duì)象被銷毀時(shí),調(diào)用接口中的方法:
ServletContextListener.contextDestroyed(ServletContextEvent sce)
-
-
ServletContext域?qū)ο蠛螘r(shí)創(chuàng)建和銷毀:
創(chuàng)建:服務(wù)器啟動(dòng)時(shí),針對(duì)每一個(gè)web應(yīng)用創(chuàng)建Servletcontext
銷毀:服務(wù)器關(guān)閉前,先關(guān)閉代表每一個(gè)web應(yīng)用的ServletContext
-
ServletContext主要用來(lái)干什么?
-
保存全局應(yīng)用數(shù)據(jù)對(duì)象
在服務(wù)器啟動(dòng)時(shí),對(duì)一些對(duì)象進(jìn)行初始化,并且將對(duì)象保存ServletContext數(shù)據(jù)范圍內(nèi) —— 實(shí)現(xiàn)全局?jǐn)?shù)據(jù)
例如:創(chuàng)建數(shù)據(jù)庫(kù)連接池
-
加載框架配置文件
- Spring框架(配置文件隨服務(wù)器啟動(dòng)加載) org.springframework.web.context.ContextLoaderListener
-
實(shí)現(xiàn)任務(wù)調(diào)度(定時(shí)器),啟動(dòng)定時(shí)程序
java.util.Timer:一種線程設(shè)施,用于安排以后在后臺(tái)線程中執(zhí)行的任務(wù),可安排任務(wù)執(zhí)行一次,或者定期重復(fù)執(zhí)行。
-
Timer提供了啟動(dòng)定時(shí)任務(wù)方法 Timer.schedule(),其中有兩種方法需要記?。?/p>
-
在指定的一個(gè)時(shí)間時(shí)啟動(dòng)定時(shí)器,定期執(zhí)行一次
Timer.schedule(TimerTask task, Date firstTime, long period) -
在當(dāng)前時(shí)間延遲多少毫秒后啟動(dòng)定時(shí)器,定期執(zhí)行一次
Timer.schedule(TimerTask task, long delay, long period)
-
-
停止定時(shí)器,取消任務(wù)
Timer.cancel()
-
-
demo:
package cn.itcast.servlet.listener; import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Timer; import java.util.TimerTask; import javax.servlet.ServletContext; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; /** * 自定義 Context監(jiān)聽器 * * @author seawind * */ public class MyServletContextListener implements ServletContextListener { @Override public void contextDestroyed(ServletContextEvent sce) { System.out.println("監(jiān)聽ServletContext對(duì)象銷毀了..."); } @Override public void contextInitialized(ServletContextEvent sce) { System.out.println("監(jiān)聽ServletContext對(duì)象創(chuàng)建了..."); // 獲得事件源 ServletContext servletContext = sce.getServletContext(); // 向ServletContext 中保存數(shù)據(jù) // 啟動(dòng)定時(shí)器 final Timer timer = new Timer(); // 啟動(dòng)定時(shí)任務(wù) // timer.schedule(new TimerTask() { // @Override // // 這就是一個(gè)線程 // public void run() { // System.out.println("定時(shí)器執(zhí)行了..."); // } // }, 0, 3000); // 馬上啟動(dòng) 每隔3秒重復(fù)執(zhí)行 // 指定時(shí)間啟動(dòng)定時(shí)器 DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); try { Date first = dateFormat.parse("2012-08-07 10:42:00"); timer.schedule(new TimerTask() { int i; @Override public void run() { i++; System.out.println("從10點(diǎn)40分開始啟動(dòng)程序,每隔3秒重復(fù)執(zhí)行"); if (i == 10) { timer.cancel();// 取消定時(shí)器任務(wù) } } }, first, 3000); } catch (ParseException e) { e.printStackTrace(); } } }
3.2、HttpSessionListener
-
HttpSessionListener接口用于監(jiān)聽HttpSession的創(chuàng)建和銷毀
-
創(chuàng)建一個(gè)Session時(shí),接口中的該方法將會(huì)被調(diào)用:
HttpSessionListener.sessionCreated(HttpSessionEvent se) -
銷毀一個(gè)Session時(shí),接口中的該方法將會(huì)被調(diào)用:
HttpSessionListener.sessionDestroyed (HttpSessionEvent se)
-
-
Session域?qū)ο髣?chuàng)建和銷毀:
創(chuàng)建:瀏覽器訪問(wèn)服務(wù)器時(shí),服務(wù)器為每個(gè)瀏覽器創(chuàng)建不同的 session 對(duì)象,服務(wù)器創(chuàng)建session
銷毀:如果用戶的session的30分鐘沒(méi)有使用,session就會(huì)過(guò)期,我們?cè)赥omcat的web.xml里面也可以配置session過(guò)期時(shí)間。
-
demo:
package cn.itcast.servlet.listener; import javax.servlet.http.HttpSession; import javax.servlet.http.HttpSessionEvent; import javax.servlet.http.HttpSessionListener; public class MyHttpSessionListener implements HttpSessionListener { @Override public void sessionCreated(HttpSessionEvent se) { // 通過(guò)事件對(duì)象獲得session 的id System.out.println("session被創(chuàng)建了"); HttpSession session = se.getSession(); System.out.println("id:" + session.getId()); } @Override public void sessionDestroyed(HttpSessionEvent se) { System.out.println("session被銷毀了"); HttpSession session = se.getSession(); System.out.println("id:" + session.getId()); } }
關(guān)于HttpSession的生命周期、具體描述詳見:JSP 會(huì)話管理
3.3、ServletRequestListener(很少用)
-
ServletRequestListener 接口用于監(jiān)聽ServletRequest 對(duì)象的創(chuàng)建和銷毀:
ServletRequest對(duì)象被創(chuàng)建時(shí),監(jiān)聽器的requestInitialized方法將會(huì)被調(diào)用。
ServletRequest對(duì)象被銷毀時(shí),監(jiān)聽器的requestDestroyed方法將會(huì)被調(diào)用。
-
ServletRequest域?qū)ο髣?chuàng)建和銷毀的時(shí)機(jī):
創(chuàng)建:用戶每一次訪問(wèn),都會(huì)創(chuàng)建一個(gè)reqeust
銷毀:當(dāng)前訪問(wèn)結(jié)束,request對(duì)象就會(huì)銷毀
-
這個(gè)監(jiān)聽器最需要注意的:
使用forward ---- request創(chuàng)建銷毀一次 (因?yàn)檗D(zhuǎn)發(fā)本質(zhì)是一次請(qǐng)求)
使用sendRedirect ---- request創(chuàng)建銷毀兩次 (因?yàn)橹囟ㄏ虮举|(zhì)是兩次請(qǐng)求)
關(guān)于ServletRequest詳見:http://www.itdecent.cn/p/7e2e3fd58e91
3.4、案例:統(tǒng)計(jì)在線人數(shù)
-
圖解:
-
首先,初始化在線人數(shù),根據(jù)前文,ServletContextListener可以監(jiān)聽ServletContext對(duì)象的創(chuàng)建,所以新建一個(gè)實(shí)現(xiàn)監(jiān)聽器接口的類:
package cn.itcast.servlet.listener.demo2; import javax.servlet.ServletContext; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; public class OnlineCountServletContextListener implements ServletContextListener { @Override public void contextDestroyed(ServletContextEvent sce) { } @Override public void contextInitialized(ServletContextEvent sce) { // 初始化在線人數(shù)為0 ServletContext context = sce.getServletContext(); context.setAttribute("onlinenum", 0); } } -
利用HttpSessionListener監(jiān)聽HttpSession對(duì)象的創(chuàng)建和銷毀,可以統(tǒng)計(jì)在線人數(shù):
package cn.itcast.servlet.listener.demo2; import javax.servlet.ServletContext; import javax.servlet.http.HttpSession; import javax.servlet.http.HttpSessionEvent; import javax.servlet.http.HttpSessionListener; public class OnlineCountHttpSessionListener implements HttpSessionListener { @Override public void sessionCreated(HttpSessionEvent se) { // 當(dāng)Session對(duì)象被創(chuàng)建時(shí),在線人數(shù) +1 HttpSession session = se.getSession(); ServletContext context = session.getServletContext(); int onlinenum = (Integer) context.getAttribute("onlinenum"); context.setAttribute("onlinenum", onlinenum + 1); System.out.println(session.getId() + "被創(chuàng)建了..."); } @Override public void sessionDestroyed(HttpSessionEvent se) { // 當(dāng)Session對(duì)象被銷毀時(shí),在線人數(shù) - 1 HttpSession session = se.getSession(); ServletContext context = session.getServletContext(); int onlinenum = (Integer) context.getAttribute("onlinenum"); context.setAttribute("onlinenum", onlinenum - 1); System.out.println(session.getId() + "被銷毀了 ..."); } } -
別忘了在web.xml中配置:
<listener> <listener-class>cn.itcast.servlet.listener.demo2.OnlineCountServletContextListener</listener-class> </listener> <listener> <listener-class>cn.itcast.servlet.listener.demo2.OnlineCountHttpSessionListener</listener-class> </listener> -
簡(jiǎn)單創(chuàng)建一個(gè)JSP頁(yè)面(注意,JSP作用域中applicationScope的范圍是整個(gè)服務(wù)器,所以可以得到ServletContext對(duì)象中存儲(chǔ)的值):
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> </head> <body> <h1>顯示在線人數(shù)</h1> ${applicationScope.onlinenum } </body> </html>
3.5、案例:利用定時(shí)器定時(shí)銷毀Session
-
圖解:
-
ScannerServletContextListener干了兩件事:初始化List<HttpSession>;啟動(dòng)定時(shí)器,每隔20秒執(zhí)行一次。
注意:
因?yàn)橐獜腖ist中刪除元素,所以循環(huán)用Iterator而不用foreach。
在使用掃描刪除Session對(duì)象時(shí),要保證Session的List集合長(zhǎng)度不能改變(即此時(shí)此刻不能添加新的Session) ---- 利用同步解決 synchronized
package cn.itcast.servlet.listener.demo3; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Timer; import java.util.TimerTask; import javax.servlet.ServletContext; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; import javax.servlet.http.HttpSession; public class ScannerServletContextListener implements ServletContextListener { @Override public void contextDestroyed(ServletContextEvent sce) { } @Override public void contextInitialized(ServletContextEvent sce) { // 第一件事,創(chuàng)建Session的List集合 final List<HttpSession> sessionList = new ArrayList<HttpSession>(); // 將集合保存ServletContext對(duì)象 ServletContext servletContext = sce.getServletContext(); servletContext.setAttribute("sessionList", sessionList); // 第二件事,啟動(dòng)定時(shí)器,每隔20秒執(zhí)行一次 Timer timer = new Timer(); timer.schedule(new TimerTask() { @Override public void run() { System.out.println("定時(shí)session掃描器執(zhí)行了...."); // 掃描Session的List集合,看哪個(gè)Session已經(jīng)1分鐘沒(méi)用了 // 發(fā)現(xiàn)Session1分鐘沒(méi)有使用,銷毀Session 從集合移除 synchronized (sessionList) { Iterator<HttpSession> iterator = sessionList.iterator(); while (iterator.hasNext()) { HttpSession session = iterator.next(); if (System.currentTimeMillis() - session.getLastAccessedTime() > 1000 * 60) { System.out.println(session.getId() + "對(duì)象已經(jīng)1分鐘沒(méi)有使用,被銷毀了..."); // 銷毀Session session.invalidate(); // 從集合移除Session iterator.remove(); } } } } }, 0, 20000); } } -
ScannerHttpSessionListener:
package cn.itcast.servlet.listener.demo3; import java.util.List; import javax.servlet.ServletContext; import javax.servlet.http.HttpSession; import javax.servlet.http.HttpSessionEvent; import javax.servlet.http.HttpSessionListener; public class ScannerHttpSessionListener implements HttpSessionListener { @Override public void sessionCreated(HttpSessionEvent se) { // 在創(chuàng)建Session對(duì)象時(shí),將Session對(duì)象加入集合 HttpSession httpSession = se.getSession(); ServletContext context = httpSession.getServletContext(); List<HttpSession> sessionList = (List<HttpSession>) context .getAttribute("sessionList"); synchronized (sessionList) { sessionList.add(httpSession); } // 還是否需要context.setAttribute? ---- 不需要:因?yàn)橹暗玫降氖荓ist的地址 context.setAttribute("sessionList", sessionList); // 這行代碼寫不寫無(wú)所謂 System.out.println(httpSession.getId() + "被創(chuàng)建了..."); } @Override public void sessionDestroyed(HttpSessionEvent se) { } } -
配置:
<listener> <listener-class>cn.itcast.servlet.listener.demo3.ScannerServletContextListener</listener-class> </listener> <listener> <listener-class>cn.itcast.servlet.listener.demo3.ScannerHttpSessionListener</listener-class> </listener>
4、監(jiān)聽三個(gè)域?qū)ο蟮膶傩?Attribute)的變化的事件監(jiān)聽器
Servlet規(guī)范定義了監(jiān)聽 ServletContext, HttpSession, HttpServletRequest 這三個(gè)對(duì)象中的屬性(Attribute)變更信息事件的監(jiān)聽器。
-
這三個(gè)監(jiān)聽器接口分別是
ServletContextAttributeListener
HttpSessionAttributeListener
ServletRequestAttributeListener
這三個(gè)接口中都定義了三個(gè)方法來(lái)處理被監(jiān)聽對(duì)象中的屬性的增加,刪除和替換的事件,同一個(gè)事件在這三個(gè)接口中對(duì)應(yīng)的方法名稱完全相同,只是接受的參數(shù)類型不同
-
XXListener.attributeAdded(XXEvent)
當(dāng)向被監(jiān)聽器對(duì)象中增加一個(gè)屬性時(shí),web容器就調(diào)用事件監(jiān)聽器的 attributeAdded 方法進(jìn)行相應(yīng),這個(gè)方法接受一個(gè)事件類型的參數(shù),監(jiān)聽器可以通過(guò)這個(gè)參數(shù)來(lái)獲得正在增加屬性的域?qū)ο蠛捅槐4娴接蛑械膶傩詫?duì)象
-
各個(gè)域?qū)傩员O(jiān)聽器中的完整語(yǔ)法定義為:
public void attributeAdded(ServletContextAttributeEvent scae) public void attributeAdded (HttpSessionBindingEvent hsbe) public void attributeAdded(ServletRequestAttributeEvent srae)
-
XXListener.attributeRemoved(XXEvent)
當(dāng)刪除被監(jiān)聽對(duì)象中的一個(gè)屬性時(shí),web 容器調(diào)用事件監(jiān)聽器的這個(gè)方法進(jìn)行相應(yīng)
-
各個(gè)域?qū)傩员O(jiān)聽器中的完整語(yǔ)法定義為:
public void attributeRemoved(ServletContextAttributeEvent scae) public void attributeRemoved (HttpSessionBindingEvent hsbe) public void attributeRemoved (ServletRequestAttributeEvent srae)
-
XXListener.attributeReplaced(XXEvent)
當(dāng)監(jiān)聽器的域?qū)ο笾械哪硞€(gè)屬性被替換時(shí),web容器調(diào)用事件監(jiān)聽器的這個(gè)方法進(jìn)行相應(yīng)
-
各個(gè)域?qū)傩员O(jiān)聽器中的完整語(yǔ)法定義為:
public void attributeReplaced(ServletContextAttributeEvent scae) public void attributeReplaced (HttpSessionBindingEvent hsbe) public void attributeReplaced (ServletRequestAttributeEvent srae)
由于這三個(gè)監(jiān)聽器用法極其相似,所以只用一個(gè)例子來(lái)演示具體用法:
-
新建有一個(gè)JSP頁(yè)面:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> </head> <body> <% // 向Session數(shù)據(jù)范圍 保存名稱為"name",值為"張三"的屬性(Attribute) session.setAttribute("name","張三"); // 觸發(fā)attributeAdd // 將session中屬性(Attribute)名為"name"的值替換為"李四" session.setAttribute("name","李四"); // 觸發(fā)attributeReplaced // 移除名為"name"的屬性 session.removeAttribute("name");// 觸發(fā)attributeRemoved %> </body> </html> -
MyHttpSessionAttributeListener:
package cn.itcast.servlet.listener; import javax.servlet.http.HttpSession; import javax.servlet.http.HttpSessionAttributeListener; import javax.servlet.http.HttpSessionBindingEvent; public class MyHttpSessionAttributeListener implements HttpSessionAttributeListener { @Override public void attributeAdded(HttpSessionBindingEvent se) { // 屬性添加 System.out.println("向session添加了一個(gè)屬性..."); HttpSession session = se.getSession(); System.out.println("屬性名稱:" + se.getName()); System.out.println("屬性值:" + session.getAttribute(se.getName())); // se.getValue :這個(gè)方法不會(huì)返回當(dāng)前屬性(Attribute)的值 } @Override public void attributeRemoved(HttpSessionBindingEvent se) { // 屬性移除 System.out.println("從session移除了一個(gè)屬性...."); System.out.println("屬性名稱:" + se.getName()); } @Override public void attributeReplaced(HttpSessionBindingEvent se) { // 屬性替換 System.out.println("將session中一個(gè)屬性值替換為其他值..."); HttpSession session = se.getSession(); System.out.println("屬性名稱:" + se.getName()); System.out.println("屬性值:" + session.getAttribute(se.getName())); } }
5、監(jiān)聽綁定到 HttpSession 域中的某個(gè)對(duì)象的狀態(tài)的事件監(jiān)聽器
-
保存在 Session 域中的對(duì)象可以有多種狀態(tài):
綁定到 Session 中;
從 Session 域中解除綁定;
隨 Session 對(duì)象持久化到一個(gè)存儲(chǔ)設(shè)備中(鈍化);
隨 Session 對(duì)象從一個(gè)存儲(chǔ)設(shè)備中恢復(fù)(活化)
-
Servlet 規(guī)范中定義了兩個(gè)特殊的監(jiān)聽器接口來(lái)幫助 JavaBean 對(duì)象了解自己在 Session 域中的這些狀態(tài):HttpSessionBindingListener 接口和 HttpSessionActivationListener 接口,實(shí)現(xiàn)這兩個(gè)接口的類不需要 web.xml 文件中注冊(cè),因?yàn)楸O(jiān)聽方法調(diào)用,都是由Session自主完成的
-
HttpSessionBindingListener
實(shí)現(xiàn)該接口的 Java 對(duì)象,可以感知自己被綁定到 Session 或者從 Session 中解除綁定
-
HttpSessionActivationListener
實(shí)現(xiàn)該接口的 Java 對(duì)象,可以感知自己從內(nèi)存被鈍化硬盤上,或者從硬盤被活化到內(nèi)存中
-
5.1、HttpSessionBindingListener接口
-
實(shí)現(xiàn)了 HttpSessionBindingListener 接口的 JavaBean 對(duì)象可以感知自己被綁定到 Session 中和從 Session 中刪除的事件
-
綁定:當(dāng)對(duì)象被綁定到 HttpSession 對(duì)象中時(shí),web 服務(wù)器調(diào)用該 JavaBean 對(duì)象的 valueBound 方法
public void valueBound(HttpSessionBindingEvent event) -
解綁:當(dāng)對(duì)象從 HttpSession 對(duì)象中解除綁定時(shí),web 服務(wù)器調(diào)用該 JavaBean 對(duì)象的 valueUnbound 方法
public void valueUnbound(HttpSessionBindingEvent event)
-
-
demo:
-
實(shí)現(xiàn) HttpSessionBindingListener 接口的 JavaBean :
package cn.itcast.domain; import javax.servlet.http.HttpSessionBindingEvent; import javax.servlet.http.HttpSessionBindingListener; /** * 使Bean1對(duì)象感知 自我被綁定Session中,感知自我被Session解除綁定 * * @author seawind * */ public class Bean1 implements HttpSessionBindingListener { private int id; private String name; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public void valueBound(HttpSessionBindingEvent event) { System.out.println("Bean1對(duì)象被綁定了..."); // 當(dāng)前對(duì)象,操作對(duì)象 System.out.println("綁定對(duì)象name:" + this.name); } @Override public void valueUnbound(HttpSessionBindingEvent event) { System.out.println("Bean1對(duì)象被解除綁定了..."); System.out.println("解除綁定對(duì)象name:" + this.name); } } -
新建一個(gè)JSP頁(yè)面,里面有如下代碼:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@page import="cn.itcast.domain.Bean1"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> </head> <body> <% Bean1 bean_Susan = new Bean1(); bean_Susan.setId(100); bean_Susan.setName("Susan"); // 將bean_Susan對(duì)象以“bean1”為名,綁定到Session中 session.setAttribute("bean1",bean_Susan); Bean1 bean_Mary = new Bean1(); bean_Mary.setId(200); bean_Mary.setName("Mary"); // 將bean_Susan對(duì)象以“bean1”為名,綁定到Session中 session.setAttribute("bean1",bean_Mary); %> ${bean1.name } </body> </html> -
此時(shí)若開啟服務(wù)器,打開這個(gè)JSP頁(yè)面,控制臺(tái)會(huì)輸出什么呢?
Bean1對(duì)象被綁定了... 綁定對(duì)象name:bean_Susan Bean1對(duì)象被綁定了... 綁定對(duì)象name:bean_Mary Bean1對(duì)象被解除綁定了... 解除綁定對(duì)象name:bean_Susan 注意陷阱:當(dāng) Session 綁定的 JavaBean 對(duì)象替換時(shí),會(huì)讓新對(duì)象綁定,舊對(duì)象解綁
-
5.2、HttpSessionActivationListener接口
-
實(shí)現(xiàn)了HttpSessionActivationListener接口的 JavaBean 對(duì)象可以感知自己被活化和鈍化的事件
當(dāng)綁定到 HttpSession 對(duì)象中的 JavaBean 對(duì)象將要隨 HttpSession 對(duì)象被鈍化之前,web 服務(wù)器調(diào)用該 JavaBean 對(duì)象的 void sessionWillPassivate(HttpSessionBindingEvent event) 方法
當(dāng)綁定到 HttpSession 對(duì)象中的 JavaBean 對(duì)象將要隨 HttpSession 對(duì)象被活化之后,web 服務(wù)器調(diào)用該 JavaBean 對(duì)象的 void sessionDidActive(HttpSessionBindingEvent event) 方法
使用場(chǎng)景:Session保存數(shù)據(jù),很長(zhǎng)一段時(shí)間沒(méi)用,但是不能銷毀Session對(duì)象,又不想占用服務(wù)器內(nèi)存資源 ----- 鈍化(將服務(wù)器內(nèi)存中數(shù)據(jù)序列化硬盤上)
-
鈍化和活化應(yīng)該由 Tomcat 服務(wù)器 自動(dòng)進(jìn)行,所以應(yīng)該配置 Tomcat :
<Context> <!-- 1分鐘不用 進(jìn)行鈍化 表示1分鐘 --> <Manager className="org.apache.catalina.session.PersistentManager" maxIdleSwap="1"> <!-- 鈍化后文件存儲(chǔ)位置 directory="it315" 存放到it315目錄--> <Store className="org.apache.catalina.session.FileStore" directory="it315"/> </Manager> </Context>配置context有幾個(gè)位置?
1、tomcat/conf/context.xml 對(duì)所有虛擬主機(jī) 所有web工程生效
2、tomcat/conf/Catalina/localhost/context.xml 對(duì)當(dāng)前虛擬主機(jī)所有web工程生效
3、當(dāng)前工程/META-INF/context.xml 對(duì)當(dāng)前工程有效
-
demo:
-
write.jsp:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@page import="cn.itcast.domain.Bean2"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> </head> <body> <!-- 將javabean 對(duì)象保存Session中 --> <% Bean2 bean2 = new Bean2(); bean2.setName("聯(lián)想筆記本"); bean2.setPrice(5000); session.setAttribute("bean2",bean2); %> </body> </html> -
read.jsp:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> </head> <body> <!-- 讀取javabean對(duì)象的數(shù)據(jù) --> 讀取bean2的數(shù)據(jù): ${bean2.name } , ${bean2.price } </body> </html> -
Bean2.java:
package cn.itcast.domain; import javax.servlet.http.HttpSessionActivationListener; import javax.servlet.http.HttpSessionEvent; /** * 感知鈍化和活化 * * @author seawind * */ public class Bean2 implements HttpSessionActivationListener { private String name; private double price; public String getName() { return name; } public void setName(String name) { this.name = name; } public double getPrice() { return price; } public void setPrice(double price) { this.price = price; } @Override public void sessionDidActivate(HttpSessionEvent se) { System.out.println("bean2對(duì)象被活化..."); } @Override public void sessionWillPassivate(HttpSessionEvent se) { System.out.println("bean2對(duì)象被鈍化..."); } } -
配置Tomcat后,開啟服務(wù)器,打開write.jsp,等待一分鐘,在這一分鐘內(nèi),打開read.jsp是可以讀取到bean2對(duì)象的。一分鐘過(guò)后,read.jsp讀取不到bean2對(duì)象了,同時(shí)控制臺(tái)輸出:
bean2對(duì)象被鈍化... -
這時(shí)候就實(shí)現(xiàn)了鈍化的效果。好,接下來(lái)按照步驟走,鈍化后it315目錄在哪里? 在項(xiàng)目文件夾是找不到這個(gè)目錄的,得去Tomcat服務(wù)器的目錄去找:
tomcat/work/Catalina/localhost/項(xiàng)目工程名/ 在it315目錄中的確可以看到一個(gè)XXXXXX.session的文件,其中XXXXXX就是SessionId,打開這個(gè)文件,卻沒(méi)有發(fā)現(xiàn) JavaBean 的任何信息,這是為什么呢?
-
回顧JavaSE的知識(shí),可以發(fā)現(xiàn) JavaBean 沒(méi)有序列化。Java對(duì)象如果想被序列化,必須實(shí)現(xiàn)Serializable接口 ---- Bean2 實(shí)現(xiàn)該接口:
import java.io.Serializable; ... public class Bean2 implements HttpSessionActivationListener, Serializable { ... } -
接下來(lái),重啟服務(wù)器,打開write.jsp,一分鐘后,控制臺(tái)照舊輸出bean2對(duì)象被鈍化的消息,再進(jìn)去XXXXXX.session文件中,發(fā)現(xiàn)可以找到 JavaBean 對(duì)象的相關(guān)信息(雖然是亂碼)。接下來(lái)再打開read.jsp,發(fā)現(xiàn)可以讀取到 JavaBean 對(duì)象了,并且此時(shí)控制臺(tái)輸出:
bean2對(duì)象被活化... 這就是一種對(duì)于Session的優(yōu)化策略
-
5.3、案例:在線用戶列表和踢人功能
-
圖解:
-
demo:
-
MyServletContextListener:
package cn.itcast.listener; import java.util.HashMap; import java.util.Map; import javax.servlet.ServletContext; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; import javax.servlet.http.HttpSession; import cn.itcast.domain.User; /** * 完成全局?jǐn)?shù)據(jù)對(duì)象初始化 * * @author seawind * */ public class MyServletContextListener implements ServletContextListener { @Override public void contextDestroyed(ServletContextEvent sce) { } @Override public void contextInitialized(ServletContextEvent sce) { // 所有在線用戶數(shù)據(jù)集合 Map<User, HttpSession> map = new HashMap<User, HttpSession>(); // 將集合保存ServletContext 數(shù)據(jù)范圍 ServletContext servletContext = sce.getServletContext(); servletContext.setAttribute("map", map); } } -
JavaBean:
package cn.itcast.domain; import java.util.Map; import javax.servlet.ServletContext; import javax.servlet.http.HttpSession; import javax.servlet.http.HttpSessionBindingEvent; import javax.servlet.http.HttpSessionBindingListener; /** * User對(duì)象自我感知,綁定Session和解除綁定 */ public class User implements HttpSessionBindingListener { private int id; private String username; private String password; private String role; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getRole() { return role; } public void setRole(String role) { this.role = role; } @Override public void valueBound(HttpSessionBindingEvent event) { // 將新建立Session 和 用戶 保存ServletContext 的Map中 HttpSession session = event.getSession(); ServletContext servletContext = session.getServletContext(); Map<User, HttpSession> map = (Map<User, HttpSession>) servletContext .getAttribute("map"); // 將新用戶加入map map.put(this, session); } @Override public void valueUnbound(HttpSessionBindingEvent event) { // 根據(jù)user對(duì)象,從Map中移除Session HttpSession session = event.getSession(); ServletContext servletContext = session.getServletContext(); Map<User, HttpSession> map = (Map<User, HttpSession>) servletContext .getAttribute("map"); // 從map移除 map.remove(this); } } -
LoginServlet:
package cn.itcast.servlet; import java.io.IOException; import java.sql.SQLException; import java.util.Map; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.apache.commons.dbutils.QueryRunner; import org.apache.commons.dbutils.handlers.BeanHandler; import cn.itcast.domain.User; import cn.itcast.utils.JDBCUtils; /** * 登陸 * * @author seawind * */ public class LoginServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("utf-8"); String username = request.getParameter("username"); String password = request.getParameter("password"); String sql = "select * from user where username = ? and password = ?"; Object[] args = { username, password }; QueryRunner queryRunner = new QueryRunner(JDBCUtils.getDataSource()); try { User user = queryRunner.query(sql, new BeanHandler<User>(User.class), args); // 判斷登陸是否成功 if (user == null) { // 失敗 request.setAttribute("msg", "用戶名或者密碼錯(cuò)誤!"); request.getRequestDispatcher("/login.jsp").forward(request, response); } else { // 成功 request.getSession().invalidate();// 銷毀之前狀態(tài) // 先判斷該用戶是否已經(jīng)登陸,如果已經(jīng)登陸,將Session銷毀 Map<User, HttpSession> map = (Map<User, HttpSession>) getServletContext() .getAttribute("map"); for (User hasLoginUser : map.keySet()) { if (hasLoginUser.getUsername().equals(user.getUsername())) { // 此用戶之前登陸過(guò) --- 消滅Session HttpSession hasLoginSession = map.get(hasLoginUser); hasLoginSession.invalidate();// session 被摧毀,移除所有對(duì)象 // 若不使用break,則會(huì)調(diào)用map的remove方法(invalidate -> valueUnbound),發(fā)生并發(fā)異常 break; } } request.getSession().setAttribute("user", user); // 將user對(duì)象綁定到Session,觸發(fā)valueBound response.sendRedirect("/day18kick/list.jsp"); } } catch (SQLException e) { e.printStackTrace(); } } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } } -
KickServlet:
package cn.itcast.servlet; import java.io.IOException; import java.util.Map; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import cn.itcast.domain.User; /** * 接收被踢id * * @author seawind * */ public class KickServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String id = request.getParameter("id");// 被踢人 id Map<User, HttpSession> map = (Map<User, HttpSession>) getServletContext() .getAttribute("map"); // 查找目標(biāo)id for (User hasLoginUser : map.keySet()) { if (hasLoginUser.getId() == Integer.parseInt(id)) { // 找到被踢用戶記錄 HttpSession hasLoginSession = map.get(hasLoginUser); hasLoginSession.invalidate(); break; } } // 跳轉(zhuǎn)回 列表頁(yè)面 response.sendRedirect("/day18kick/list.jsp"); } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } } -
list.jsp:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> </head> <body> <h1>在線用戶列表</h1> <h2>當(dāng)前用戶 ${user.username }</h2> <!-- 將ServletContext中 map 數(shù)據(jù)顯示出來(lái) --> <c:forEach items="${map}" var="entry"> <!-- 只有管理員可以踢人 --> <!-- 管理員不能被踢 --> ${entry.key.username } <c:if test="${user.role == 'admin' && entry.key.role != 'admin' }"> <a href="/day18kick/kick?id=${entry.key.id}">踢下線</a> </c:if> <br/> </c:forEach> </body> </html>
-




