監聽器【第二篇應用】

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

統計網站在線人數

分析

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

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

大體思路:安全

  • 監聽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。bash

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

這裏寫圖片描述


自定義Session掃描器

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

可是呢,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的容器應該是在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對象建立的時候寫的。---------- //獲得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屬性的變化就不同了,登記的是具體用戶是否作了某事(登錄、購買了某商品)

相關文章
相關標籤/搜索