監聽器應用【統計網站人數、自定義session掃描器、踢人小案例】

從第一篇已經講解過了監聽器的基本概念,以及Servlet各類的監聽器。這篇博文主要講解的是監聽器的應用。html

統計網站在線人數

分析

咱們在網站中通常使用Session來標識某用戶是否登錄了,若是登錄了,就在Session域中保存相對應的屬性。若是沒有登錄,那麼Session的屬性就應該爲空。java

如今,咱們想要統計的是網站的在線人數。咱們應該這樣作:咱們監聽是否有新的Session建立了,若是新建立了Sesssion,那麼在線人數就應該+1。這個在線人數是整個站點的,因此應該有Context對象保存。web

大體思路:瀏覽器

  • 監聽Session是否被建立了
  • 若是Session被建立了,那麼在Context的域對象的值就應該+1
  • 若是Session從內存中移除了,那麼在Context的域對象的值就應該-1.

代碼

  • 監聽器代碼:
public class CountOnline implements HttpSessionListener {
    
        public void sessionCreated(HttpSessionEvent se) {
    
            //獲取獲得Context對象,使用Context域對象保存用戶在線的個數
            ServletContext context = se.getSession().getServletContext();
            
            //直接判斷Context對象是否存在這個域,若是存在就人數+1,若是不存在,那麼就將屬性設置到Context域中
            Integer num = (Integer) context.getAttribute("num");
            
            if (num == null) {
                context.setAttribute("num", 1);
            } else {
                num++;
                context.setAttribute("num", num);
            }
        }
        public void sessionDestroyed(HttpSessionEvent se) {
    
            ServletContext context = se.getSession().getServletContext();
            Integer num = (Integer) se.getSession().getAttribute("num");
    
            if (num == null) {
                context.setAttribute("num", 1);
            } else {
                num--;
                context.setAttribute("num", num);
            }
        }
    }
  • 顯示頁面代碼:
在線人數:${num}

測試

咱們每使用一個瀏覽器訪問服務器,都會新建立一個Session。那麼網站的在線人數就會+1。安全

使用同一個頁面刷新,仍是使用的是那個Sesssion,因此網站的在線人數是不會變的。服務器

這裏寫圖片描述


自定義Session掃描器

咱們都知道Session是保存在內存中的,若是Session過多,服務器的壓力就會很是大。微信

可是呢,Session的默認失效時間是30分鐘(30分鐘沒人用纔會失效),這形成Seesion可能會過多(沒人用也存在內存中,這不是明顯浪費嗎?)session

固然啦,咱們能夠在web.xml文件中配置Session的生命週期。可是呢,這是由服務器來作的,我嫌它的時間不夠準確。(有時候我配置了3分鐘,它用4分鐘才幫我移除掉Session)併發

因此,我決定本身用程序手工移除那些長時間沒人用的Session。jsp

分析

要想移除長時間沒人用的Session,確定要先拿到所有的Session啦。因此咱們使用一個容器來裝載站點全部的Session。。

只要Sesssion一建立了,就把Session添加到容器裏邊。毫無疑問的,咱們須要監聽Session了。

接着,咱們要作的就是隔一段時間就去掃描一下所有Session,若是有Session長時間沒使用了,咱們就把它從內存中移除。隔一段時間去作某事,這確定是定時器的任務呀。

定時器應該在服務器一啓動的時候,就應該被建立了。所以還須要監聽Context

最後,咱們還要考慮到併發的問題,若是有人同時訪問站點,那麼監聽Session建立的方法就會被併發訪問了定時器掃描容器的時候,多是獲取不到全部的Session的

這須要咱們作同步

因而乎,咱們已經有大體的思路了

  • 監聽Session和Context的建立
  • 使用一個容器來裝載Session
  • 定時去掃描Session,若是它長時間沒有使用到了,就把該Session從內存中移除。
  • 併發訪問的問題

代碼

  • 監聽器代碼:
public class Listener1 implements ServletContextListener,
            HttpSessionListener {
    
    
    
        //服務器一啓動,就應該建立容器。咱們使用的是LinkList(涉及到增刪)。容器也應該是線程安全的。
        List<HttpSession> list = Collections.synchronizedList(new LinkedList<HttpSession>());
    
        //定義一把鎖(Session添加到容器和掃描容器這兩個操做應該同步起來)
        private Object lock = 1;
    
        public void contextInitialized(ServletContextEvent sce) {
    
    
            Timer timer = new Timer();
            //執行我想要的任務,0秒延時,每10秒執行一次
            timer.schedule(new MyTask(list, lock), 0, 10 * 1000);
    
        }
        public void sessionCreated(HttpSessionEvent se) {
    
            //只要Session一建立了,就應該添加到容器中
            synchronized (lock) {
                list.add(se.getSession());
            }
            System.out.println("Session被建立啦");
    
        }
    
        public void sessionDestroyed(HttpSessionEvent se) {
            System.out.println("Session被銷燬啦。");
        }
        public void contextDestroyed(ServletContextEvent sce) {
    
        }
    }
  • 任務代碼:
/*
    * 在任務中應該掃描容器,容器在監聽器上,只能傳遞進來了。
    *
    * 要想獲得在監聽器上的鎖,也只能是傳遞進來
    *
    * */
    class MyTask extends TimerTask {
    
        private List<HttpSession> sessions;
        private Object lock;
    
        public MyTask(List<HttpSession> sessions, Object lock) {
            this.sessions = sessions;
            this.lock = lock;
        }
    
        @Override
        public void run() {
    
            synchronized (lock) {
                //遍歷容器
                for (HttpSession session : sessions) {
    
                    //只要15秒沒人使用,我就移除它啦
                    if (System.currentTimeMillis() - session.getLastAccessedTime() > (1000 * 15)) {
                        session.invalidate();
                        sessions.remove(session);
                    }
    
                }
            }
        }
    }
  • 測試:

15秒若是Session沒有活躍,那麼就被刪除!

這裏寫圖片描述

  • 使用集合來裝載咱們全部的Session
  • 使用定時器來掃描session的聲明週期【因爲定時器沒有session,咱們傳進去就行了】
  • 關於併發訪問的問題,咱們在掃描和檢測session添加的時候,同步起來就行了【固然,定時器的鎖也是要外面傳遞進來的】

踢人小案列

列出全部的在線用戶,後臺管理者擁有踢人的權利,點擊踢人的超連接,該用戶就被註銷了。

分析

首先,怎麼能列出全部的在線用戶呢??通常咱們在線用戶都是用Session來標記的,全部的在線用戶就應該用一個容器來裝載全部的Session。。

咱們監聽Session的是否有屬性添加(監聽Session的屬性有添加、修改、刪除三個方法。若是監聽到Session添加了,那麼這個確定是個在線用戶!)。

裝載Session的容器應該是在Context裏邊的【屬於全站點】,而且容器應該使用Map集合【待會還要經過用戶的名字來把用戶踢了】

思路:

  • 寫監聽器,監聽是否有屬性添加在Session裏邊了
  • 寫簡單的登錄頁面。
  • 列出全部的在線用戶
  • 實現踢人功能(也就是摧毀Session)

代碼

  • 監聽器
public class KickPerson implements HttpSessionAttributeListener {

    // Public constructor is required by servlet spec
    public KickPerson() {
    }

    public void attributeAdded(HttpSessionBindingEvent sbe) {

        //獲得context對象,看看context對象是否有容器裝載Session
        ServletContext context = sbe.getSession().getServletContext();

        //若是沒有,就建立一個唄
        Map map = (Map) context.getAttribute("map");
        if (map == null) {
            map = new HashMap();
            context.setAttribute("map", map);
        }

        //---------------------------------------------------------------------------------------
        
        //獲得Session屬性的值
        Object o = sbe.getValue();

        //判斷屬性的內容是不是User對象
        if (o instanceof User) {
            User user = (User) o;
            map.put(user.getUsername(), sbe.getSession());
        }
    }

    public void attributeRemoved(HttpSessionBindingEvent sbe) {
      /* This method is called when an attribute
         is removed from a session.
      */
    }

    public void attributeReplaced(HttpSessionBindingEvent sbe) {
      /* This method is invoked when an attibute
         is replaced in a session.
      */
    }
}
  • 登錄頁面
<form action="${pageContext.request.contextPath }/LoginServlet" method="post">
    用戶名:<input type="text" name="username">
    <input type="submit" value="登錄">
</form>
  • 處理登錄Servlet
//獲得傳遞過來的數據
        String username = request.getParameter("username");

        User user = new User();
        user.setUsername(username);

        //標記該用戶登錄了!
        request.getSession().setAttribute("user", user);

        //提供界面,告訴用戶登錄是否成功
        request.setAttribute("message", "恭喜你,登錄成功了!");
        request.getRequestDispatcher("/message.jsp").forward(request, response);
  • 列出在線用戶
<c:forEach items="${map}" var="me">

    ${me.key} <a href="${pageContext.request.contextPath}/KickPersonServlet?username=${me.key}">踢了他吧</a>

    <br>
</c:forEach>
  • 處理踢人的Servlet
String username = request.getParameter("username");

        //獲得裝載全部的Session的容器
        Map map = (Map) this.getServletContext().getAttribute("map");

        //經過名字獲得Session
        HttpSession httpSession = (HttpSession) map.get(username);
        httpSession.invalidate();
        map.remove(username);

        //摧毀完Session後,返回列出在線用戶頁面
        request.getRequestDispatcher("/listUser.jsp").forward(request, response);

測試

使用多個瀏覽器登錄來模擬在線用戶(同一個瀏覽器使用的都是同一個Session)

這裏寫圖片描述


監聽Seesion的建立和監聽Session屬性的變化有啥區別???

  • Session的建立只表明着瀏覽器給服務器發送了請求。會話創建
  • Session屬性的變化就不同了,登記的是具體用戶是否作了某事(登錄、購買了某商品)
若是文章有錯的地方歡迎指正,你們互相交流。習慣在微信看技術文章的同窗,能夠關注微信公衆號:Java3y
相關文章
相關標籤/搜索