一、監聽器就是一個實現特定接口的普通java程序,這個程序專門用於監聽一個java對象的方法調用或屬性改變,當被監聽對象發生上述事件後,監聽器某個方法將當即被執行.java
二、監聽器典型案例:監聽window窗口的事件監聽器jquery
public class Demo1 { /** * 面試題:請描述一下java事件監聽機制.(和jquery事件徹底相似) * 1.java的事件監聽機制涉及到三個組件:事件源、事件監聽器、事件對象 * 2.當事件源上發生操做時,它將會調用事件監聽器的一個方法,並在調用這個方法時,會傳遞事件對象過來. * 3.事件監聽器由開發人員編寫,開發人員在事件監聽器中,經過事件對象能夠拿到事件源,從而對事件源上進行處理. */ public static void main(String[] args) { Frame f = new Frame();//事件源 f.setSize(400, 400); f.setVisible(true); //這種傳遞實現接口的對象屬於策略設計模式的應用 f.addWindowListener(new MyListener());//註冊事件監聽器 } } class MyListener implements WindowListener{ public void windowClosing(WindowEvent e) { Frame f = (Frame) e.getSource(); f.dispose(); } public void windowDeactivated(WindowEvent e) { } ... }
三、本身實現監聽器web
//觀察者設計模式(一般用來處理事件系統) //事件源 class Person{ private PersonListener listener; public void registerListener(PersonListener listener){ this.listener = listener; } public void run(){ if(listener!=null){ Even even = new Even(this); this.listener.dorun(even); } System.out.println("runn!!"); } public void eat(){ if(listener!=null){ Even e = new Even(this); this.listener.doeat(e); } System.out.println("eat!!"); } } //事件監聽器 interface PersonListener{ public void dorun(Even even); public void doeat(Even even); } //事件對象(封裝事件源) class Even{ private Person person; public Even() { super(); } public Even(Person person) { super(); this.person = person; } public Person getPerson() { return person; } public void setPerson(Person person) { this.person = person; } } public class Demo3 { public static void main(String[] args) { Person p = new Person(); p.registerListener(new MyListener1()); p.eat(); p.run(); } } class MyListener1 implements PersonListener{ public void doeat(Even even) { System.out.println(even.getPerson()+"你每天吃,你就知道吃,你豬啊!!"); } public void dorun(Even even) { System.out.println(even.getPerson()+"你吃完就跑,有病!!"); } }
四、一個面試題,多個客戶在餐廳併發點餐,如今只有一個打印機,打印出單子交給廚房,系統該怎麼作?面試
一種方法是每隔10秒就檢查一次,檢測到就打印,這種很差,有cpu空轉問題,應該是用個監聽器取監聽這個裝菜單的容器(這個容器應該同步),只要它一add就執行監聽器方法打印菜單.spring
ps:觀察者模式的一個例子:求職者先在獵頭處註冊,當有新的工做機會時獵頭就會通知求職者.設計模式
一、servlet監聽器分類api
在Servlet規範中定義了多種類型的監聽器,它們用於監聽的事件源分別爲 ServletContext, HttpSession 和 ServletRequest 這三個域對象.Servlet規範針對這三個對象上的操做,又把這多種類型的監聽器劃分爲三種類型.瀏覽器
•監聽三個域對象建立和銷燬的事件監聽器安全
•監聽域對象中屬性的增長和刪除的事件監聽器服務器
•監聽綁定到 HttpSession 域中的某個對象的狀態的事件監聽器.(查看API文檔),也就是某個javabean對象能夠監聽本身是否被放入HttpSession域中(無需在web.xml中配置,用的很少).
二、編寫 Servlet 監聽器
一、ServletContextListener 接口用於監聽 ServletContext 對象的建立和銷燬事件.
當 ServletContext 對象被建立時,激發contextInitialized (ServletContextEvent sce)方法
當 ServletContext 對象被銷燬時,激發contextDestroyed(ServletContextEvent sce)方法.
public class MyServletContextListener implements ServletContextListener { public void contextInitialized(ServletContextEvent sce) { System.out.println("servletContext被建立了!"); } public void contextDestroyed(ServletContextEvent sce) { System.out.println("servletcontext被銷燬了!!"); } }
//web.xml,這裏web服務器經過反射建立MyServletContextListener對象而後註冊給ServletContext
<listener> <listener-class>cn.itcast.web.listener.ServletContextListener</listener-class> </listener>
提問,servletContext域對象什麼時候建立和銷燬:
•建立:服務器啓動針對每個web應用建立servletcontext.
•銷燬:服務器關閉前先關閉表明每個web應用的servletContext.
ps:實際開發能夠用來初始化配置文件,例如spring中這麼使用.
二、HttpSessionListener接口用於監聽HttpSession的建立和銷燬.
提問,Session域對象什麼時候建立和銷燬:
•建立:用戶每一次訪問時,服務器建立session
•銷燬:若是用戶的session 30分鐘沒有使用,服務器就會銷燬session,咱們在web.xml裏面也能夠配置session失效時間
注意:
1>若是直接訪問jsp頁面會建立session,由於直接訪問jsp會被web服務器轉爲Servlet,而後傳給Servet八大隱式對象,直接訪問Servlet不會建立session.
2>刷新頁面不會從新建立session,基於一個瀏覽器彈出來的都共用一個session,關了瀏覽器session不會摧毀,駐留內存30分鐘不用自動摧毀.
3>客戶端禁用cookie,刷新頁面會一直建立session,由於沒有帶sessionID過來,服務器會認爲是一次新的回話.
ps:實際開發能夠用來統計瀏覽器使用率,來訪者ip等.
三、ServletRequestListener用於監聽ServletRequest 對象的建立和銷燬.
提問,ServletRequest域對象什麼時候建立和銷燬:
•建立:用戶每一次訪問,都會建立一個reqeust,sendRedirect會建立,forward不會.
•銷燬:當前訪問結束,request對象就會銷燬
ps:實際開發能夠用來統計網站點擊量.
四、監聽域對象中屬性的增長和刪除的事件監聽器(用的很少)
一、這三個監聽器接口分別是ServletContextAttributeListener, HttpSessionAttributeListener ServletRequestAttributeListener.這三個接口中都定義了三個方法來處理被監聽對象中的屬性的增長,刪除和替換的事件,同一個事件在這三個接口中對應的方法名稱徹底相同,只是接受的參數類型不一樣.
二、當向被監聽器對象中增長一個屬性時,web容器就調用事件監聽器的 attributeAdded 方法進行響應,這個方法接受一個事件類型的參數,監聽器能夠經過這個參數來得到正在增長屬性的域對象和被保存到域中的屬性對象.
//ServletContextAttributeListener public class MyServletContextAttributeListener implements ServletContextAttributeListener { public void attributeAdded(ServletContextAttributeEvent scab) { String name = scab.getName(); Object value = scab.getValue(); System.out.println("向servletContext中存了:" + name + "=" + value); } public void attributeRemoved(ServletContextAttributeEvent scab) { System.out.println("從servletcontext中刪除了:" + scab.getName()); } public void attributeReplaced(ServletContextAttributeEvent scab) { System.out.println("servletcontext中" + scab.getName() + "屬性被替換了"); } } //HttpSessionAttributeListener,ServletRequestAttributeListener public class HttpSessionAndServletRequestAttributeListener implements HttpSessionAttributeListener, ServletRequestAttributeListener { public void attributeAdded(HttpSessionBindingEvent se) { System.out.println("向session中加入東西了!!"); } public void attributeRemoved(HttpSessionBindingEvent se) { System.out.println("從session中刪了東西!!"); } public void attributeReplaced(HttpSessionBindingEvent se) { System.out.println("把session中的屬性替換了!!"); } public void attributeAdded(ServletRequestAttributeEvent srae) { System.out.println("向request中加入東西了!!"); } public void attributeRemoved(ServletRequestAttributeEvent srae) { System.out.println("從request中刪了東西!!"); } public void attributeReplaced(ServletRequestAttributeEvent srae) { System.out.println("把request中的屬性替換了!!"); } }
一、統計網站當前在線用戶
public class CountNumListener implements HttpSessionListener { public void sessionCreated(HttpSessionEvent se) { ServletContext context = se.getSession().getServletContext(); Integer count = (Integer) context.getAttribute("count"); if(count==null){ count = 1; }else{ count++; } context.setAttribute("count", count); } public void sessionDestroyed(HttpSessionEvent se) { ServletContext context = se.getSession().getServletContext(); Integer count = (Integer) context.getAttribute("count"); count--; context.setAttribute("count", count); } } //直接以下寫法是不行的,沒法在頁面中取到,通常是顯示在首頁中. public class CountNumListener implements HttpSessionListener { int count; public void sessionCreated(HttpSessionEvent se) { count++; } public void sessionDestroyed(HttpSessionEvent se) { count--; } }
二、自定義session掃描器,本身來管理session.
//將session裝入集合本身管理,定時器每隔一段時間執行一次,session超過一段時間未使用,則刪除. public class SessionScanner implements HttpSessionListener,ServletContextListener { //Collections這個集合工具類生成的集合是線程安全的. private List<HttpSession> list = Collections.synchronizedList(new LinkedList<HttpSession>()); private Object lock = new Object(); public void contextInitialized(ServletContextEvent sce) { Timer timer = new Timer(); timer.schedule(new MyTask(list,lock), 0, 30*1000); } public void sessionCreated(HttpSessionEvent se) { HttpSession session = se.getSession(); System.out.println(session + "被建立了!!"); synchronized (lock) { //鎖旗標 list.add(session); } } public void sessionDestroyed(HttpSessionEvent se) { System.out.println(se.getSession() + "被銷燬了"); } public void contextDestroyed(ServletContextEvent sce) { } } class MyTask extends TimerTask{ private List list; private Object lock; public MyTask(List list,Object lock){ this.list = list; this.lock = lock; } @Override public void run() { System.out.println("定時器執行!!"); synchronized (this.lock) { ListIterator it = list.listIterator(); while(it.hasNext()){ HttpSession session = (HttpSession) it.next(); if((System.currentTimeMillis()-session.getLastAccessedTime())>30*1000){ session.invalidate(); //list.remove(session); //併發修改異常 it.remove(); } } } } }
ps:
class MyList{ Object arr[] = new Object[10]; public void add(Object obj){ //session if(arr[0]==null){ arr[0] = obj; } ... } }
如上是自定義的一個集合,顯示多個線程共同調用add方法時存在併發問題的緣由,在if(arr[0]==null)後cpu切換,會形成集合元素的覆蓋,併發環境下這裏應該使用線程安全的集合Collections.synchronizedList或vector(線程安全的Arraylist),vector如今已經不推薦使用,Collections有將各類集合變爲線程安全集合的方法.這裏有一個注意問題:
List list = Collections.synchronizedList(new ArrayList()); ... synchronized(list) { Iterator i = list.iterator(); // Must be in synchronized block while (i.hasNext()) foo(i.next()); }
synchronizedList在迭代的時候,須要開發者本身加上線程鎖控制代碼,爲何呢
由於迭代器涉及的代碼沒有在java api中沒有加上線程同步代碼.
整個迭代的過程當中若是在循環外面不加同步代碼,在一次次迭代之間,其餘線程對於這個容器的add或者remove會影響整個迭代的預期效果,因此這裏須要用戶在整個循環外面加上synchronized(list),這也是迭代中remove元素時出現併發訪問異常的緣由,感受用for循環更好,就不用加鎖了.
三、定時發郵件,如今通常使用線程池的定時任務代替傳統的定時器.略
四、實現了HttpSessionActivationListener接口的 JavaBean 對象能夠感知本身被活化和鈍化的事件(用的很少)
//對象要寫入硬盤必須序列化,實現Serializable 接口 public class MyBean implements HttpSessionActivationListener,Serializable { public void sessionDidActivate(HttpSessionEvent se) { System.out.println("javabean隨着session從硬盤迴到內存了!!"); } public void sessionWillPassivate(HttpSessionEvent se) { System.out.println("javabean隨着session到硬盤中去了!!"); } }
五、網站實現踢人效果(就是將用戶的session中去除),略.