1. 程式人生 > >JavaWeb學習 (二十七)————監聽器(Listener)在開發中的應用

JavaWeb學習 (二十七)————監聽器(Listener)在開發中的應用

  監聽器在JavaWeb開發中用得比較多,下面說一下監聽器(Listener)在開發中的常見應用

一、統計當前線上人數

  在JavaWeb應用開發中,有時候我們需要統計當前線上的使用者數,此時就可以使用監聽器技術來實現這個功能了。

複製程式碼
 1 package me.gacl.web.listener;
 2 
 3 import javax.servlet.ServletContext;
 4 import javax.servlet.http.HttpSessionEvent;
 5 import javax.servlet.http.HttpSessionListener;
 6 
 7 /**
 8 * @ClassName: OnLineCountListener
 9 * @Description: 統計當前線上使用者個數
10 * @author: 孤傲蒼狼
11 * @date: 2014-9-10 下午10:01:26
12 *
13 */ 
14 public class OnLineCountListener implements HttpSessionListener {
15 
16     @Override
17     public void sessionCreated(HttpSessionEvent se) {
18         ServletContext context = se.getSession().getServletContext();
19         Integer onLineCount = (Integer) context.getAttribute("onLineCount");
20         if(onLineCount==null){
21             context.setAttribute("onLineCount", 1);
22         }else{
23             onLineCount++;
24             context.setAttribute("onLineCount", onLineCount);
25         }
26     }
27 
28     @Override
29     public void sessionDestroyed(HttpSessionEvent se) {
30         ServletContext context = se.getSession().getServletContext();
31         Integer onLineCount = (Integer) context.getAttribute("onLineCount");
32         if(onLineCount==null){
33             context.setAttribute("onLineCount", 1);
34         }else{
35             onLineCount--;
36             context.setAttribute("onLineCount", onLineCount);
37         }
38     }
39 }
複製程式碼

二、自定義Session掃描器

  當一個Web應用建立的Session很多時,為了避免Session佔用太多的記憶體,我們可以選擇手動將這些記憶體中的session銷燬,那麼此時也可以藉助監聽器技術來實現。

複製程式碼
  1 package me.gacl.web.listener;
  2 
  3 import java.util.Collections;
  4 import java.util.LinkedList;
  5 import java.util.List;
  6 import java.util.ListIterator;
  7 import java.util.Timer;
  8 import java.util.TimerTask;
  9 import javax.servlet.ServletContextEvent;
 10 import javax.servlet.ServletContextListener;
 11 import javax.servlet.http.HttpSession;
 12 import javax.servlet.http.HttpSessionEvent;
 13 import javax.servlet.http.HttpSessionListener;
 14 
 15 /**
 16 * @ClassName: SessionScanerListener
 17 * @Description: 自定義session掃描器
 18 * @author: 孤傲蒼狼
 19 * @date: 2014-9-10 下午10:16:42
 20 * 
 21 */ 
 22 public class SessionScanerListener implements HttpSessionListener,ServletContextListener {
 23 
 24     /**
 25     * @Field: list
 26     *          定義一個集合儲存伺服器建立的HttpSession
 27     *        LinkedList不是一個執行緒安全的集合
 28     */ 
 29     /**
 30      * private List<HttpSession> list = new LinkedList<HttpSession>();
 31      * 這樣寫涉及到執行緒安全問題,SessionScanerListener物件在記憶體中只有一個
 32      * sessionCreated可能會被多個人同時呼叫,
 33      * 當有多個人併發訪問站點時,伺服器同時為這些併發訪問的人建立session
 34      * 那麼sessionCreated方法在某一時刻內會被幾個執行緒同時呼叫,幾個執行緒併發呼叫sessionCreated方法
 35      * sessionCreated方法的內部處理是往一個集合中新增建立好的session,那麼在加session的時候就會
 36      * 涉及到幾個Session同時搶奪集合中一個位置的情況,所以往集合中新增session時,一定要保證集合是執行緒安全的才行
 37      * 如何把一個集合做成執行緒安全的集合呢?
 38      * 可以使用使用 Collections.synchronizedList(List<T> list)方法將不是執行緒安全的list集合包裝執行緒安全的list集合
 39      */
 40     //使用 Collections.synchronizedList(List<T> list)方法將LinkedList包裝成一個執行緒安全的集合
 41     private List<HttpSession> list = Collections.synchronizedList(new LinkedList<HttpSession>());
 42     //定義一個物件,讓這個物件充當一把鎖,用這把鎖來保證往list集合新增的新的session和遍歷list集合中的session這兩個操作達到同步
 43     private Object lock = new Object();
 44             
 45     @Override
 46     public void sessionCreated(HttpSessionEvent se) {
 47         System.out.println("session被建立了!!");
 48         HttpSession session = se.getSession();
 49         
 50         synchronized (lock){
 51             /**
 52              *將該操作加鎖進行鎖定,當有一個thread-1(執行緒1)在呼叫這段程式碼時,會先拿到lock這把鎖,然後往集合中新增session,
 53              *在新增session的這個過程中假設有另外一個thread-2(執行緒2)來訪問了,thread-2可能是執行定時器任務的,
 54              *當thread-2要呼叫run方法遍歷list集合中的session時,結果發現遍歷list集合中的session的那段程式碼被鎖住了,
 55              *而這把鎖正在被往集合中新增session的那個thread-1佔用著,因此thread-2只能等待thread-1操作完成之後才能夠進行操作
 56              *當thread-1新增完session之後,就把lock放開了,此時thread-2拿到lock,就可以執行遍歷list集合中的session的那段程式碼了
 57              *通過這把鎖就保證了往集合中新增session和變數集合中的session這兩步操作不能同時進行,必須按照先來後到的順序來進行。
 58              */
 59             list.add(session);
 60         }
 61     }
 62 
 63     @Override
 64     public void sessionDestroyed(HttpSessionEvent se) {
 65         System.out.println("session被銷燬了了!!");
 66     }
 67 
 68     /* Web應用啟動時觸發這個事件
 69      * @see javax.servlet.ServletContextListener#contextInitialized(javax.servlet.ServletContextEvent)
 70      */
 71     @Override
 72     public void contextInitialized(ServletContextEvent sce) {
 73         System.out.println("web應用初始化");
 74         //建立定時器
 75         Timer timer = new Timer();
 76         //每隔30秒就定時執行任務
 77         timer.schedule(new MyTask(list,lock), 0, 1000*30);
 78     }
 79 
 80     @Override
 81     public void contextDestroyed(ServletContextEvent sce) {
 82         System.out.println("web應用關閉");
 83     }
 84 }
 85 
 86 /**
 87 * @ClassName: MyTask
 88 * @Description:定時器要定時執行的任務
 89 * @author: 孤傲蒼狼
 90 * @date: 2014-9-11 上午12:02:36
 91 *
 92 */ 
 93 class MyTask extends TimerTask {
 94         
 95     //儲存HttpSession的list集合
 96     private List<HttpSession> list;
 97     //儲存傳遞過來的鎖
 98     private Object lock;
 99     public MyTask(List<HttpSession> list,Object lock){
100         this.list = list;
101         this.lock = lock;
102     }
103     /* run方法指明瞭任務要做的事情
104      * @see java.util.TimerTask#run()
105      */
106     @Override
107     public void run() {
108             //將該操作加鎖進行鎖定
109         synchronized (lock) {
110             System.out.println("定時器執行!!");
111             ListIterator<HttpSession> it = list.listIterator();
112             /**
113              * 迭代list集合中的session,在迭代list集合中的session的過程中可能有別的使用者來訪問,
114              * 使用者一訪問,伺服器就會為該使用者建立一個session,此時就會呼叫sessionCreated往list集合中新增新的session,
115              * 然而定時器在定時執行掃描遍歷list集合中的session時是無法知道正在遍歷的list集合又新增的新的session進來了,
116              * 這樣就導致了往list集合新增的新的session和遍歷list集合中的session這兩個操作無法達到同步
117              * 那麼解決的辦法就是把"list.add(session)和while(it.hasNext()){//迭代list集合}"這兩段程式碼做成同步,
118              * 保證當有一個執行緒在訪問"list.add(session)"這段程式碼時,另一個執行緒就不能訪問"while(it.hasNext()){//迭代list集合}"這段程式碼
119              * 為了能夠將這兩段不相干的程式碼做成同步,只能定義一把鎖(Object lock),然後給這兩步操作加上同一把鎖,
120              * 用這把鎖來保證往list集合新增的新的session和遍歷list集合中的session這兩個操作達到同步
121              * 當在執行往list集合新增的新的session操作時,就必須等新增完成之後才能夠對list集合進行迭代操作,
122              * 當在執行對list集合進行迭代操作時,那麼必須等到迭代操作結束之後才能夠往往list集合新增的新的session
123              */
124             while(it.hasNext()){
125                 HttpSession session = (HttpSession) it.next();
126                 /**
127                  * 如果當前時間-session的最後訪問時間>1000*15(15秒)
128                  * session.getLastAccessedTime()獲取session的最後訪問時間
129                  */
130                 if(System.currentTimeMillis()-session.getLastAccessedTime()>1000*30){
131                     //手動銷燬session
132                     session.invalidate();
133                     //移除集合中已經被銷燬的session
134                     it.remove();
135                 }
136             }
137         }
138     }
139 }
複製程式碼

 以上就是監聽器的兩個簡單應用場景。