一、概述
監聽器就是一個實現特定接口的普通java程序,這個程序專門用于監聽另一個java對象的方法調用或屬性改變,當被監聽對象發生上述事件后,監聽器某個方法將立即被執行。
二、監聽器經典案例:監聽windows窗口的事件監聽器
(工程day20
)
請描述java時間監聽機制:
- 1.事件監聽涉及到三個組件:事件源、事件對象、事件監聽器
- 2.當事件源上發生某個動作時,它會調用事件監聽器的一個方法,并在調用該方法時把事件對象傳遞進去,開發人員在監聽器中通過事件對象,就可以拿到事件源,從而對事件源進行操作。事件對象封裝事件源和動作,而監聽器對象通過事件對象對事件源進行處理。
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("關閉");
Frame f = (Frame) e.getSource();//得到關閉窗口的事件源
f.dispose();//關閉窗口
}
public void windowClosed(WindowEvent arg0) {}
public void windowActivated(WindowEvent arg0) {}
});
}
}
說明:這里我們產生一個窗口,當我們點擊窗口右上角的叉時,使用監聽器監測此事件,點擊的時候就會監測到,執行關閉操作,這是一個經典的監聽器使用例子。上例中使用方法addWindowListener
注冊一個監聽器,在監聽器中使用相關方法對事件源進行處理,當然我們會將事件源WindowEvent
傳遞進去。
三、自己設計一個類讓別人監聽
Demo2.java
package cn.itcast.demo;
//設計一個事件源,被監聽器監聽,Observer(觀察者設計模式)
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{//讓這個類被其他類監聽
private PersonListener listener;//定義一個監聽器接口,記住傳遞進來的監聽器對象
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
和一個監聽器接口PersonListener
,然后我們想讓某個類(這里是Person
)被監聽,于是需要在類中維護一個監聽器接口PersonListener
,我們可以使用一個方法(registerListener
)將此接口傳遞進來,然后我們就可以使用監聽器接口中的相關方法對事件源進行處理了。
四、servlet監聽器
在servlet規范中定義了多種類型的監聽器,它們用于監聽的事件源分別為
ServletContext、HttpSession 和 ServletRequest
這三個域對象。-
Servlet規范針對這三個對象上的操作,又把這多種類型的監聽器劃分為三種類型:
- 1.監聽三個域對象創建和銷毀的事件監聽器;
- 2.監聽域對象中屬性的增加和刪除的事件監聽器;
- 3.監聽綁定到
HttpSession
域中的某個對象的狀態的事件監聽器。
-
監聽
servletContext
域對象創建和銷毀-
ServletContextListener
接口用于監聽ServletContext
對象的創建和銷毀事件。 - 當
ServletContext
對象被創建時,激發````contextInitialized(ServletContextEvent sce)```方法。 - 當
ServletContext
對象被銷毀時,激發contextDestroyed(ServletContextEvent sce)
方法。
-
注意:ServletContext
域對象何時創建和銷毀?
- 創建:服務器啟動針對每一個web應用創建一個
ServletContext
。 - 銷毀:服務器關閉前先關閉代表每一個web應用的
ServletContext
。
4.1 示例:監聽ServletContext
對象
MyServletContextListener.java
package cn.itcast.web.listener;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
//這里我們只需要在web.xml文件中將此監聽器配置就可以了,當服務器啟動時就會創建ServletContext
public class MyServletContextListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("ServletContext創建");
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("ServletContext銷毀");
}
}
在web.xml
中進行注冊:
<listener>
<listener-class>cn.itcast.web.listener.MyServletContextListener</listener-class>
</listener>
說明:因為在服務器啟動的時候ServletContext
就會創建,這時我們可以監測到其創建。
4.2 監聽HttpSession
域對象創建和銷毀
這里HttpSessionListener
接口用于監聽HttpSession
的創建和銷毀。
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創建了");
}
@Override
public void sessionDestroyed(HttpSessionEvent se) {
System.out.println("session銷毀了");
}
}
說明:在訪問index.jsp
的時候會創建一個session
,服務器關閉的時候是不會摧毀session
的,我們可以設置失效時間,在配置文件中進行配置,單位是分鐘,可以用來統計當前在線多少用戶,但是不是特別準確。
Session
域對象創建和銷毀的時機
創建:用戶每一次訪問時,服務器創建Session
。
銷毀:如果用戶的Session
三十分鐘(默認)沒有使用,服務器就會銷毀Session
,我們在web.xml
里面也可以配置Session
失效時間。
4.3 監聽HttpRequest
域對象創建和銷毀
這里ServletRequestListener
接口用于監聽ServletRequest
對象的創建和銷毀。
MyServletRequestListener .java
package cn.itcast.web.listener;
//ServletRequestListener可以用來檢測網站性能
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() + "創建了");
}
}
說明:ServletRequest
域對象創建和銷毀的時機
創建:用戶每一次訪問,都會創建一個Request。
銷毀:當前訪問結束,Request對象就會銷毀。
五、案例:統計當前在線人數
OnlineCountListener.java
package cn.itcast.web.listener;
//統計當前在線用戶個數
import javax.servlet.ServletContext;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
//監聽器和過濾器一樣,Servlet中只存在一個,所以num不需要設置成靜態的
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);
}
}
}
說明:當服務器啟動時只有這個監聽器只有一個,所以我們可以在方法中定義一個變量來統計在線人數。而這個變量我們如果要傳遞到前臺,不能使用request和session,因為會有多個。這里我們通過servletContext域來將此統計值傳遞到前臺。
index.jsp
<body>
當前在線用戶個數:${applicationScope.num}
</body>
六、案例:自定義Session掃描器
在開發中我們有時候需要管理session,比如當session多長時間沒用之后我們就將其銷毀,減小服務器的壓力。
SessionScannerListener.java
package cn.itcast.web.listener;
//Session的默認失效時間是三十分鐘
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);//這樣做容易出現兩個Session搶一個list位置的情況,即集合不是線程安全的
System.out.println("被創建了");
}
@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);//將其從當前的list中移除
it.remove();//調用迭代器將其移除
}
}
}
/*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);//將其從當前的list中移除
it.remove();//調用迭代器將其移除
}
}*/
}
}
說明:
1.我們定義一個集合來保存所有
session
,但是當兩個用戶同時訪問的時候,有可能在創建session
的時存入集合的同一個位置,為了避免這種情況,我們將集合做成一個線程安全的,java中為我們提供了一個集合幫助類Collections
類,可以將集合做成一個線程安全的集合。2.我們要掃描在線用戶,所以需要定義一個定時器,而此定時器是在服務器一啟動就需要開啟,于是我們還需要一個
servletContext
的監聽器,我們直接讓定義的監聽器繼承兩個監聽器接口,同時監聽HttsSession
和servletContext
。3.我們在遍歷集合的時候是不能執行add操作的,這會出現并發問題,所以我們需要給迭代器和add方法都加上一把鎖,防止并發問題。將一段代碼做成同步是只需要加關鍵字synchronized即可,但是如果要把兩段代碼做成同步的就需要用到鎖。
我們還可以指定服務器在某個時間發送郵件:
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;
//我們設置一個時間,讓監聽器在設置的時間點干什么事情
public class SendMailListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
Calendar c = Calendar.getInstance();
c.set(2015, 11, 7, 15, 11, 0);//設置一個時間是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) {}
}
七、監聽三個域對象屬性的變化
Servlet規范定義了監聽
ServletContext、HttpSession
和
HttpServletRequest
這三個對象中的屬性變更信息事件的監聽器。這三個監聽器接口分別是
ServletContextAttributeListener、HttpSessionAttributeListener、ServletRequestAttributeListener
。這三個接口中都定義了三個方法來處理被監聽對象中的屬性的增加,刪除和替換的事件,同一個事件在這三個接口中對應的方法名稱完全相同,只是接受的參數類型不同。
八、相關方法
8.1attributeAdded
方法
當向被監聽對象中增加一個屬性時,web容器就調用事件監聽器的
attributeAdded
方法進行添加操作,這個方法接受一個事件類型的參數,監聽器可以通過這個參數來獲得正在增加屬性的域對象和被保護到域中的屬性對象。各個域屬性監聽器中的完整語法定義
public void attributeAdded(ServletContextAttributeEvent scae)
public void attributeAdded(HttpSessionBindingEvent se)
public void attributeAdded(ServletRequestAttributeEvent srae)
8.2 attributeRemoved
方法
- 當刪除被監聽對象中的一個屬性時,web容器調用事件監聽器的這個方法進行相應的操作。
- 各個域屬性監聽器中的完整語法定義
public void attributeRemoved(ServletContextAttributeEvent scab)
public void attributeRemoved(HttpSessionBindingEvent se)
public void attributeRemoved(ServletRequestAttributeEvent srae)
8.3 attributeReplace
方法
- 當監聽器的域對象中的某一個屬性被替換時,web容器調用事件監聽器的這個方法進行相應的操作。
- 各個域屬性監聽器中的完整語法定義
public void attributeReplaced(ServletContextAttributeEvent scab)
public void attributeReplaced(HttpSessionBindingEvent se)
public void attributeReplaced(ServletRequestAttributeEvent srae)
8.4 感知Session
綁定的事件監聽器
保存在
Session
域中的對象可以有多種狀態。綁定到Session
中:從Session
域中解決綁定;隨Session
對象持久化到一個存儲設備中;隨Session
對象從一個存儲設備中恢復。servlet規范中定義兩個特殊的監聽器接口來幫助
javaBean
對象了解自己在Session
域中的這些狀態:HttpSessionBindingListener
接口和HttpSessionActivationListener
接口,實現這兩個接口的類不需要在web.xml
文件中進行注冊。HttpSessionBindingListener
接口
實現了此接口的javaBean
對象可以感知自己被綁定到Session
中和從Session
中刪除的事件。
例:MyBean .java
package cn.itcast.domain;
//這個監聽器用來監聽自己,所以不需要在配置文件中進行配置
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
接口
實現了此接口的javaBean對象可以感知自己被活化和鈍化的事件。
MyBean2.java
package cn.itcast.domain;
import java.io.Serializable;
import javax.servlet.http.HttpSessionActivationListener;
import javax.servlet.http.HttpSessionEvent;
//注意:Session被鈍化和活化都是由tomcat管理,默認是三十分鐘,但是我們也可以自己進行設置。更改服務器的配置
public class MyBean2 implements HttpSessionActivationListener,Serializable {
@Override
public void sessionWillPassivate(HttpSessionEvent se) {
System.out.println("鈍化");//即從內存中序列化到硬盤
}
@Override
public void sessionDidActivate(HttpSessionEvent se) {
System.out.println("活化");//從硬盤中回到內存
}
}
同時我們需要一個配置文件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>