监听器应用【统计网站人数、自定义session扫描器、踢人小案例】

从第一篇已经讲解过了监听器的基本概念,以及Servlet各类的监听器。这篇博文主要讲解的是监听器的应用。web

统计网站在线人数

分析

咱们在网站中通常使用Session来标识某用户是否登录了,若是登录了,就在Session域中保存相对应的属性。若是没有登录,那么Session的属性就应该为空。浏览器

如今,咱们想要统计的是网站的在线人数。咱们应该这样作:咱们监听是否有新的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。微信

使用同一个页面刷新,仍是使用的是那个Sesssion,因此网站的在线人数是不会变的。session

 

这里写图片描述

 

自定义Session扫描器

咱们都知道Session是保存在内存中的,若是Session过多,服务器的压力就会很是大。并发

可是呢,Session的默认失效时间是30分钟(30分钟没人用才会失效),这形成Seesion可能会过多(没人用也存在内存中,这不是明显浪费吗?)jsp

固然啦,咱们能够在web.xml文件中配置Session的生命周期。可是呢,这是由服务器来作的,我嫌它的时间不够准确。(有时候我配置了3分钟,它用4分钟才帮我移除掉Session)ide

因此,我决定本身用程序手工移除那些长时间没人用的Session。post

分析

要想移除长时间没人用的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

相关文章
相关标签/搜索