一、概述
監(jiān)聽器就是一個實現(xiàn)特定接口的普通java程序,這個程序?qū)iT用于監(jiān)聽另一個java對象的方法調(diào)用或?qū)傩愿淖?,?dāng)被監(jiān)聽對象發(fā)生上述事件后,監(jiān)聽器某個方法將立即被執(zhí)行。
二、監(jiān)聽器經(jīng)典案例:監(jiān)聽windows窗口的事件監(jiān)聽器
(工程day20)
請描述java時間監(jiān)聽機制:
- 1.事件監(jiān)聽涉及到三個組件:事件源、事件對象、事件監(jiān)聽器
- 2.當(dāng)事件源上發(fā)生某個動作時,它會調(diào)用事件監(jiān)聽器的一個方法,并在調(diào)用該方法時把事件對象傳遞進去,開發(fā)人員在監(jiān)聽器中通過事件對象,就可以拿到事件源,從而對事件源進行操作。事件對象封裝事件源和動作,而監(jiān)聽器對象通過事件對象對事件源進行處理。
Demo1.java
package cn.itcast.demo;
import java.awt.Frame;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
public class Demo1 {
public static void main(String[] args) {
Frame f = new Frame();
f.setSize(400, 400);
f.setVisible(true);
f.addWindowListener(new WindowListener() {
public void windowOpened(WindowEvent arg0) {}
public void windowIconified(WindowEvent arg0) {}
public void windowDeiconified(WindowEvent arg0) {}
public void windowDeactivated(WindowEvent arg0) {}
@Override
public void windowClosing(WindowEvent e) {
System.out.println("關(guān)閉");
Frame f = (Frame) e.getSource();//得到關(guān)閉窗口的事件源
f.dispose();//關(guān)閉窗口
}
public void windowClosed(WindowEvent arg0) {}
public void windowActivated(WindowEvent arg0) {}
});
}
}
說明:這里我們產(chǎn)生一個窗口,當(dāng)我們點擊窗口右上角的叉時,使用監(jiān)聽器監(jiān)測此事件,點擊的時候就會監(jiān)測到,執(zhí)行關(guān)閉操作,這是一個經(jīng)典的監(jiān)聽器使用例子。上例中使用方法addWindowListener注冊一個監(jiān)聽器,在監(jiān)聽器中使用相關(guān)方法對事件源進行處理,當(dāng)然我們會將事件源WindowEvent傳遞進去。
三、自己設(shè)計一個類讓別人監(jiān)聽
Demo2.java
package cn.itcast.demo;
//設(shè)計一個事件源,被監(jiān)聽器監(jiān)聽,Observer(觀察者設(shè)計模式)
public class Demo2 {
public static void main(String[] args) {
Person p = new Person();
p.registerListener(new PersonListener() {
@Override
public void dorun(Event e) {
Person person = e.getSource();
System.out.println(person + "吃飯");
}
@Override
public void doeat(Event e) {
Person person = e.getSource();
System.out.println(person + "跑步");
}
});
p.eat();
}
}
class Person{//讓這個類被其他類監(jiān)聽
private PersonListener listener;//定義一個監(jiān)聽器接口,記住傳遞進來的監(jiān)聽器對象
public void eat(){
if(listener != null){
listener.doeat(new Event(this));
}
}
public void run(){
if(listener != null){
listener.dorun(new Event(this));
}
}
public void registerListener(PersonListener listener){
this.listener = listener;
}
}
interface PersonListener{
public void doeat(Event e);
public void dorun(Event e);
}
class Event{//用于封裝事件源
private Person source;
public Event() {
super();
}
public Event(Person source) {
super();
this.source = source;
}
public Person getSource() {
return source;
}
public void setSource(Person source) {
this.source = source;
}
}
說明:首先我們定義事件源對象Event和一個監(jiān)聽器接口PersonListener,然后我們想讓某個類(這里是Person)被監(jiān)聽,于是需要在類中維護一個監(jiān)聽器接口PersonListener,我們可以使用一個方法(registerListener)將此接口傳遞進來,然后我們就可以使用監(jiān)聽器接口中的相關(guān)方法對事件源進行處理了。
四、servlet監(jiān)聽器
在servlet規(guī)范中定義了多種類型的監(jiān)聽器,它們用于監(jiān)聽的事件源分別為
ServletContext、HttpSession 和 ServletRequest這三個域?qū)ο蟆?/p>-
Servlet規(guī)范針對這三個對象上的操作,又把這多種類型的監(jiān)聽器劃分為三種類型:
- 1.監(jiān)聽三個域?qū)ο髣?chuàng)建和銷毀的事件監(jiān)聽器;
- 2.監(jiān)聽域?qū)ο笾袑傩缘脑黾雍蛣h除的事件監(jiān)聽器;
- 3.監(jiān)聽綁定到
HttpSession域中的某個對象的狀態(tài)的事件監(jiān)聽器。
-
監(jiān)聽
servletContext域?qū)ο髣?chuàng)建和銷毀-
ServletContextListener接口用于監(jiān)聽ServletContext對象的創(chuàng)建和銷毀事件。 - 當(dāng)
ServletContext對象被創(chuàng)建時,激發(fā)````contextInitialized(ServletContextEvent sce)```方法。 - 當(dāng)
ServletContext對象被銷毀時,激發(fā)contextDestroyed(ServletContextEvent sce)方法。
-
注意:ServletContext域?qū)ο蠛螘r創(chuàng)建和銷毀?
- 創(chuàng)建:服務(wù)器啟動針對每一個web應(yīng)用創(chuàng)建一個
ServletContext。 - 銷毀:服務(wù)器關(guān)閉前先關(guān)閉代表每一個web應(yīng)用的
ServletContext。
4.1 示例:監(jiān)聽ServletContext對象
MyServletContextListener.java
package cn.itcast.web.listener;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
//這里我們只需要在web.xml文件中將此監(jiān)聽器配置就可以了,當(dāng)服務(wù)器啟動時就會創(chuàng)建ServletContext
public class MyServletContextListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("ServletContext創(chuàng)建");
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("ServletContext銷毀");
}
}
在web.xml中進行注冊:
<listener>
<listener-class>cn.itcast.web.listener.MyServletContextListener</listener-class>
</listener>
說明:因為在服務(wù)器啟動的時候ServletContext就會創(chuàng)建,這時我們可以監(jiān)測到其創(chuàng)建。
4.2 監(jiān)聽HttpSession域?qū)ο髣?chuàng)建和銷毀
這里HttpSessionListener接口用于監(jiān)聽HttpSession的創(chuàng)建和銷毀。
MyHttpSessionListener.java
package cn.itcast.web.listener;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
public class MyHttpSessionListener implements HttpSessionListener {
@Override
public void sessionCreated(HttpSessionEvent se) {
System.out.println(se.getSession() + "session創(chuàng)建了");
}
@Override
public void sessionDestroyed(HttpSessionEvent se) {
System.out.println("session銷毀了");
}
}
說明:在訪問index.jsp的時候會創(chuàng)建一個session,服務(wù)器關(guān)閉的時候是不會摧毀session的,我們可以設(shè)置失效時間,在配置文件中進行配置,單位是分鐘,可以用來統(tǒng)計當(dāng)前在線多少用戶,但是不是特別準(zhǔn)確。
Session域?qū)ο髣?chuàng)建和銷毀的時機
創(chuàng)建:用戶每一次訪問時,服務(wù)器創(chuàng)建Session。
銷毀:如果用戶的Session三十分鐘(默認(rèn))沒有使用,服務(wù)器就會銷毀Session,我們在web.xml里面也可以配置Session失效時間。
4.3 監(jiān)聽HttpRequest域?qū)ο髣?chuàng)建和銷毀
這里ServletRequestListener接口用于監(jiān)聽ServletRequest對象的創(chuàng)建和銷毀。
MyServletRequestListener .java
package cn.itcast.web.listener;
//ServletRequestListener可以用來檢測網(wǎng)站性能
import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
public class MyServletRequestListener implements ServletRequestListener {
@Override
public void requestDestroyed(ServletRequestEvent sre) {
System.out.println(sre.getServletRequest() + "銷毀了");
}
@Override
public void requestInitialized(ServletRequestEvent sre) {
System.out.println(sre.getServletRequest() + "創(chuàng)建了");
}
}
說明:ServletRequest域?qū)ο髣?chuàng)建和銷毀的時機
創(chuàng)建:用戶每一次訪問,都會創(chuàng)建一個Request。
銷毀:當(dāng)前訪問結(jié)束,Request對象就會銷毀。
五、案例:統(tǒng)計當(dāng)前在線人數(shù)
OnlineCountListener.java
package cn.itcast.web.listener;
//統(tǒng)計當(dāng)前在線用戶個數(shù)
import javax.servlet.ServletContext;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
//監(jiān)聽器和過濾器一樣,Servlet中只存在一個,所以num不需要設(shè)置成靜態(tài)的
public class OnlineCountListener implements HttpSessionListener {
//如果我們要將num值傳遞到頁面,則不能使用Request和session,而只能通過Application(ServletContext)
/*int num = 0;*/
@Override
public void sessionCreated(HttpSessionEvent se) {
ServletContext context = se.getSession().getServletContext();
Integer num = (Integer) context.getAttribute("num");
if(num == null){
context.setAttribute("num", 1);
}else{
num++;
context.setAttribute("num", num);
}
}
@Override
public void sessionDestroyed(HttpSessionEvent se) {
ServletContext context = se.getSession().getServletContext();
Integer num = (Integer) context.getAttribute("num");
if(num == null){
context.setAttribute("num", 1);
}else{
num--;
context.setAttribute("num", num);
}
}
}
說明:當(dāng)服務(wù)器啟動時只有這個監(jiān)聽器只有一個,所以我們可以在方法中定義一個變量來統(tǒng)計在線人數(shù)。而這個變量我們?nèi)绻獋鬟f到前臺,不能使用request和session,因為會有多個。這里我們通過servletContext域來將此統(tǒng)計值傳遞到前臺。
index.jsp
<body>
當(dāng)前在線用戶個數(shù):${applicationScope.num}
</body>
六、案例:自定義Session掃描器
在開發(fā)中我們有時候需要管理session,比如當(dāng)session多長時間沒用之后我們就將其銷毀,減小服務(wù)器的壓力。
SessionScannerListener.java
package cn.itcast.web.listener;
//Session的默認(rèn)失效時間是三十分鐘
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Timer;
import java.util.TimerTask;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
//自定義Session掃描器
public class SessionScannerListener implements HttpSessionListener, ServletContextListener{
private List<HttpSession> list = Collections.synchronizedList(new LinkedList());//使得集合成為一個線程安全的集合
private Object lock;//定義一把鎖
@Override
public void sessionCreated(HttpSessionEvent se) {
HttpSession session = se.getSession();
synchronized (lock) {
list.add(session);
}
//list.add(session);//這樣做容易出現(xiàn)兩個Session搶一個list位置的情況,即集合不是線程安全的
System.out.println("被創(chuàng)建了");
}
@Override
public void sessionDestroyed(HttpSessionEvent se) {
System.out.println("被銷毀了");
}
@Override
public void contextInitialized(ServletContextEvent sce) {
Timer timer = new Timer() ;
timer.schedule(new MyTask(list,lock), 0, 1000*15);//延時為0,每隔15秒掃描一次
}
@Override
public void contextDestroyed(ServletContextEvent sce) {}
}
class MyTask extends TimerTask{
private List<HttpSession> list ;
private Object lock;//定義一把鎖用于記住傳遞進來的鎖
public MyTask(List list, Object lock) {//將要掃描的集合傳遞進來
this.list = list;
this.lock = lock;
}
@Override
public void run() {
synchronized (lock) {
ListIterator<HttpSession> it = list.listIterator();
while(it.hasNext()){
HttpSession session = (HttpSession) it.next();
if((System.currentTimeMillis() - session.getLastAccessedTime()) > 1000*60){//表示此Session十五秒沒人用,就將其摧毀
session.invalidate();//摧毀此Session
//list.remove(session);//將其從當(dāng)前的list中移除
it.remove();//調(diào)用迭代器將其移除
}
}
}
/*ListIterator<HttpSession> it = list.listIterator();
while(it.hasNext()){
HttpSession session = (HttpSession) it.next();
if((System.currentTimeMillis() - session.getLastAccessedTime()) > 1000*60){//表示此Session十五秒沒人用,就將其摧毀
session.invalidate();//摧毀此Session
//list.remove(session);//將其從當(dāng)前的list中移除
it.remove();//調(diào)用迭代器將其移除
}
}*/
}
}
說明:
1.我們定義一個集合來保存所有
session,但是當(dāng)兩個用戶同時訪問的時候,有可能在創(chuàng)建session的時存入集合的同一個位置,為了避免這種情況,我們將集合做成一個線程安全的,java中為我們提供了一個集合幫助類Collections類,可以將集合做成一個線程安全的集合。2.我們要掃描在線用戶,所以需要定義一個定時器,而此定時器是在服務(wù)器一啟動就需要開啟,于是我們還需要一個
servletContext的監(jiān)聽器,我們直接讓定義的監(jiān)聽器繼承兩個監(jiān)聽器接口,同時監(jiān)聽HttsSession和servletContext。3.我們在遍歷集合的時候是不能執(zhí)行add操作的,這會出現(xiàn)并發(fā)問題,所以我們需要給迭代器和add方法都加上一把鎖,防止并發(fā)問題。將一段代碼做成同步是只需要加關(guān)鍵字synchronized即可,但是如果要把兩段代碼做成同步的就需要用到鎖。
我們還可以指定服務(wù)器在某個時間發(fā)送郵件:
SendMailListener.java
package cn.itcast.web.listener;
import java.util.Calendar;
import java.util.Timer;
import java.util.TimerTask;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
//我們設(shè)置一個時間,讓監(jiān)聽器在設(shè)置的時間點干什么事情
public class SendMailListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
Calendar c = Calendar.getInstance();
c.set(2015, 11, 7, 15, 11, 0);//設(shè)置一個時間是2015.12.7 15:11:00
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("aaaaaaaa");
}
}, c.getTime());
}
@Override
public void contextDestroyed(ServletContextEvent sce) {}
}
七、監(jiān)聽三個域?qū)ο髮傩缘淖兓?/h1>
Servlet規(guī)范定義了監(jiān)聽ServletContext、HttpSession和
HttpServletRequest這三個對象中的屬性變更信息事件的監(jiān)聽器。
這三個監(jiān)聽器接口分別是ServletContextAttributeListener、HttpSessionAttributeListener、ServletRequestAttributeListener。
這三個接口中都定義了三個方法來處理被監(jiān)聽對象中的屬性的增加,刪除和替換的事件,同一個事件在這三個接口中對應(yīng)的方法名稱完全相同,只是接受的參數(shù)類型不同。
八、相關(guān)方法
8.1attributeAdded方法
當(dāng)向被監(jiān)聽對象中增加一個屬性時,web容器就調(diào)用事件監(jiān)聽器的attributeAdded方法進行添加操作,這個方法接受一個事件類型的參數(shù),監(jiān)聽器可以通過這個參數(shù)來獲得正在增加屬性的域?qū)ο蠛捅槐Wo到域中的屬性對象。
各個域?qū)傩员O(jiān)聽器中的完整語法定義
public void attributeAdded(ServletContextAttributeEvent scae)
public void attributeAdded(HttpSessionBindingEvent se)
public void attributeAdded(ServletRequestAttributeEvent srae)
8.2 attributeRemoved方法
- 當(dāng)刪除被監(jiān)聽對象中的一個屬性時,web容器調(diào)用事件監(jiān)聽器的這個方法進行相應(yīng)的操作。
- 各個域?qū)傩员O(jiān)聽器中的完整語法定義
public void attributeRemoved(ServletContextAttributeEvent scab)
public void attributeRemoved(HttpSessionBindingEvent se)
public void attributeRemoved(ServletRequestAttributeEvent srae)
8.3 attributeReplace方法
- 當(dāng)監(jiān)聽器的域?qū)ο笾械哪骋粋€屬性被替換時,web容器調(diào)用事件監(jiān)聽器的這個方法進行相應(yīng)的操作。
- 各個域?qū)傩员O(jiān)聽器中的完整語法定義
public void attributeReplaced(ServletContextAttributeEvent scab)
public void attributeReplaced(HttpSessionBindingEvent se)
public void attributeReplaced(ServletRequestAttributeEvent srae)
8.4 感知Session綁定的事件監(jiān)聽器
保存在Session域中的對象可以有多種狀態(tài)。綁定到Session中:從Session域中解決綁定;隨Session對象持久化到一個存儲設(shè)備中;隨Session對象從一個存儲設(shè)備中恢復(fù)。
servlet規(guī)范中定義兩個特殊的監(jiān)聽器接口來幫助javaBean對象了解自己在Session域中的這些狀態(tài):HttpSessionBindingListener接口和HttpSessionActivationListener接口,實現(xiàn)這兩個接口的類不需要在web.xml文件中進行注冊。
HttpSessionBindingListener接口
實現(xiàn)了此接口的javaBean對象可以感知自己被綁定到Session中和從Session中刪除的事件。
例:MyBean .java
package cn.itcast.domain;
//這個監(jiān)聽器用來監(jiān)聽自己,所以不需要在配置文件中進行配置
import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionBindingListener;
public class MyBean implements HttpSessionBindingListener {
private String name;
@Override
public void valueBound(HttpSessionBindingEvent event) {
System.out.println("自己被添加到Session");
}
@Override
public void valueUnbound(HttpSessionBindingEvent event) {
System.out.println("自己被從Session刪除");
}
}
Servlet規(guī)范定義了監(jiān)聽ServletContext、HttpSession和
HttpServletRequest這三個對象中的屬性變更信息事件的監(jiān)聽器。
這三個監(jiān)聽器接口分別是ServletContextAttributeListener、HttpSessionAttributeListener、ServletRequestAttributeListener。
這三個接口中都定義了三個方法來處理被監(jiān)聽對象中的屬性的增加,刪除和替換的事件,同一個事件在這三個接口中對應(yīng)的方法名稱完全相同,只是接受的參數(shù)類型不同。
attributeAdded方法當(dāng)向被監(jiān)聽對象中增加一個屬性時,web容器就調(diào)用事件監(jiān)聽器的attributeAdded方法進行添加操作,這個方法接受一個事件類型的參數(shù),監(jiān)聽器可以通過這個參數(shù)來獲得正在增加屬性的域?qū)ο蠛捅槐Wo到域中的屬性對象。
各個域?qū)傩员O(jiān)聽器中的完整語法定義
public void attributeAdded(ServletContextAttributeEvent scae)
public void attributeAdded(HttpSessionBindingEvent se)
public void attributeAdded(ServletRequestAttributeEvent srae)
attributeRemoved方法public void attributeRemoved(ServletContextAttributeEvent scab)
public void attributeRemoved(HttpSessionBindingEvent se)
public void attributeRemoved(ServletRequestAttributeEvent srae)
attributeReplace方法public void attributeReplaced(ServletContextAttributeEvent scab)
public void attributeReplaced(HttpSessionBindingEvent se)
public void attributeReplaced(ServletRequestAttributeEvent srae)
Session綁定的事件監(jiān)聽器保存在Session域中的對象可以有多種狀態(tài)。綁定到Session中:從Session域中解決綁定;隨Session對象持久化到一個存儲設(shè)備中;隨Session對象從一個存儲設(shè)備中恢復(fù)。
servlet規(guī)范中定義兩個特殊的監(jiān)聽器接口來幫助javaBean對象了解自己在Session域中的這些狀態(tài):HttpSessionBindingListener接口和HttpSessionActivationListener接口,實現(xiàn)這兩個接口的類不需要在web.xml文件中進行注冊。
HttpSessionBindingListener接口
實現(xiàn)了此接口的javaBean對象可以感知自己被綁定到Session中和從Session中刪除的事件。
例:MyBean .java
package cn.itcast.domain;
//這個監(jiān)聽器用來監(jiān)聽自己,所以不需要在配置文件中進行配置
import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionBindingListener;
public class MyBean implements HttpSessionBindingListener {
private String name;
@Override
public void valueBound(HttpSessionBindingEvent event) {
System.out.println("自己被添加到Session");
}
@Override
public void valueUnbound(HttpSessionBindingEvent event) {
System.out.println("自己被從Session刪除");
}
}
index.jsp
<% session.setAttribute("bean", new MyBean()); %>
-
HttpSessionActivationListener接口
實現(xiàn)了此接口的javaBean對象可以感知自己被活化和鈍化的事件。
MyBean2.java
package cn.itcast.domain;
import java.io.Serializable;
import javax.servlet.http.HttpSessionActivationListener;
import javax.servlet.http.HttpSessionEvent;
//注意:Session被鈍化和活化都是由tomcat管理,默認(rèn)是三十分鐘,但是我們也可以自己進行設(shè)置。更改服務(wù)器的配置
public class MyBean2 implements HttpSessionActivationListener,Serializable {
@Override
public void sessionWillPassivate(HttpSessionEvent se) {
System.out.println("鈍化");//即從內(nèi)存中序列化到硬盤
}
@Override
public void sessionDidActivate(HttpSessionEvent se) {
System.out.println("活化");//從硬盤中回到內(nèi)存
}
}
同時我們需要一個配置文件context.xml,放在META-INF中。
<Context>
<manager className="org.apache.catalina.session.PersistentManager" maxIdleSwap="1">1表示一分鐘
<Store className="org.apache.catalina.session.FileStore" directory="it315"/> it315這個目錄在tomcat的work目錄中找到
</manager>
</Context>