一揭鳞、概述
監(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)聽機(jī)制:
- 1.事件監(jiān)聽涉及到三個組件:事件源乓梨、事件對象鳖轰、事件監(jiān)聽器
- 2.當(dāng)事件源上發(fā)生某個動作時清酥,它會調(diào)用事件監(jiān)聽器的一個方法,并在調(diào)用該方法時把事件對象傳遞進(jìn)去脆霎,開發(fā)人員在監(jiān)聽器中通過事件對象总处,就可以拿到事件源,從而對事件源進(jìn)行操作睛蛛。事件對象封裝事件源和動作鹦马,而監(jiān)聽器對象通過事件對象對事件源進(jì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)方法對事件源進(jìn)行處理和悦,當(dāng)然我們會將事件源WindowEvent
傳遞進(jìn)去。
三渠缕、自己設(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)聽器接口,記住傳遞進(jì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)聽馍忽,于是需要在類中維護(hù)一個監(jiān)聽器接口PersonListener
,我們可以使用一個方法(registerListener
)將此接口傳遞進(jìn)來燕差,然后我們就可以使用監(jiān)聽器接口中的相關(guān)方法對事件源進(jì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
中進(jìn)行注冊:
<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è)置失效時間支竹,在配置文件中進(jìn)行配置,單位是分鐘,可以用來統(tǒng)計當(dāng)前在線多少用戶,但是不是特別準(zhǔn)確鸠按。
Session
域?qū)ο髣?chuàng)建和銷毀的時機(jī)
創(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)建和銷毀的時機(jī)
創(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;//定義一把鎖用于記住傳遞進(jìn)來的鎖
public MyTask(List list, Object lock) {//將要掃描的集合傳遞進(jìn)來
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
方法進(jìn)行添加操作,這個方法接受一個事件類型的參數(shù)芦疏,監(jiān)聽器可以通過這個參數(shù)來獲得正在增加屬性的域?qū)ο蠛捅槐Wo(hù)到域中的屬性對象冕杠。
各個域?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)聽器的這個方法進(jì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)聽器的這個方法進(jì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
文件中進(jìn)行注冊。
HttpSessionBindingListener
接口
實現(xiàn)了此接口的javaBean
對象可以感知自己被綁定到Session
中和從Session
中刪除的事件昆稿。
例:MyBean .java
package cn.itcast.domain;
//這個監(jiān)聽器用來監(jiān)聽自己纺座,所以不需要在配置文件中進(jì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
方法進(jìn)行添加操作,這個方法接受一個事件類型的參數(shù)芦疏,監(jiān)聽器可以通過這個參數(shù)來獲得正在增加屬性的域?qū)ο蠛捅槐Wo(hù)到域中的屬性對象冕杠。
各個域?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
文件中進(jìn)行注冊。
HttpSessionBindingListener
接口
實現(xiàn)了此接口的javaBean
對象可以感知自己被綁定到Session
中和從Session
中刪除的事件昆稿。
例:MyBean .java
package cn.itcast.domain;
//這個監(jiān)聽器用來監(jiān)聽自己纺座,所以不需要在配置文件中進(jì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)是三十分鐘净响,但是我們也可以自己進(jì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>