案例:统计当前在线用户数量

使用监听器实现在线客户统计

统计连接在应用上的客户端数量。客户端的唯一标识就是 IP,只需要对连接到服务器上
的 IP 数量进行统计,就可统计出客户端的数量。网上很多这方面的文章都是只监听了session,即当session创建时,在线客户端的数量就+1,session销毁时,在线客户端的数量就-1,这种统计的方法不正确,用户在同一台机器上打开两个不同的浏览器访问系统时,系统会创建两个session对象,如果按照上面方式统计的话当前在线客户有两个,而实际上只有一个,因此需要通过客户端的ip地址来判断的方式更加准确。

统计系统在线用户量分析

1.创建ServletContext 监听器
在 ServletContext 初始化时创建一个Map对象,该Map对象用于存放 IP 信息,并将创建好的 Map 存放到 ServletContext 域中。Map的key为客户端 IP,而 value 则为该客户端 IP 所发出的session对象组成的 List。

package com.monkey1024.listener;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.http.HttpSession;

public class MyServletContextListener implements ServletContextListener {

    /*
     * 监听ServletContext 初始化时创建一个Map对象
     */
    @Override
    public void contextInitialized(ServletContextEvent sce) {

        //map的key存放ip,value存放该ip地址生成的session对象的List集合
        Map<String,List<HttpSession>> ipMap = new HashMap<>();

        ServletContext sc = sce.getServletContext();
        //将创建好的map对象放到ServletContext域中
        sc.setAttribute("ipMap", ipMap);
    }


}

2.定义 Request 监听器
在该监听器中主要实现下面功能:

  • 获取当前请求的客户端的 IP
  • 获取当前 IP 所对应的全局域中的 List。若这个 List 为空,则创建一个 List。
  • 将当前 IP 放入到 List 中,并将当前 IP 与 List 写入到 Map 中,然后再重新将 Map 写回ServletContext 中
  • 将当前 IP 存放到当前请求所对应的 Session 域中,以备在 Session 销毁时使用
package com.monkey1024.listener;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletContext;
import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

public class MyRequestListener implements ServletRequestListener {


    @Override
    public void requestInitialized(ServletRequestEvent sre) {
        //获取请求对象
        HttpServletRequest request = (HttpServletRequest) sre.getServletRequest();
        //获取请求的ip
        String ip = request.getRemoteAddr();
        System.out.println(ip);
        ServletContext sc = sre.getServletContext();
        //从ServletContext中获取ipMap
        Map<String,List<HttpSession>> ipMap = (Map<String, List<HttpSession>>) sc.getAttribute("ipMap");
        //获取key为当前ip的list
        List<HttpSession> list = ipMap.get(ip);
        if(list == null){
            list = new ArrayList<>();
        }

        //获取当前请求所关联的session
        HttpSession currentSession = request.getSession();

        //遍历该list,如果存在请求所关联的session对象,则说明这是同一个会话中的请求,无需处理。
        for(HttpSession s : list){
            if(s == currentSession){
                return;
            }
        }

        //当上面条件不满足时,说明是该ip创建的一个新的session对象,需要将该对象加入到list中
        list.add(currentSession);
        //将list添加到map中
        ipMap.put(ip, list);
        //将ipMap重新放到ServletContext域中
        sc.setAttribute("ipMap", ipMap);

        //将ip放到session中
        currentSession.setAttribute("ip", ip);
    }


}

3.定义 Session 监听器
该监听器的功能主要是,当 Session 被销毁时, 将当前 Session 对象从 List 中删除。在从
List 删除后,若 List 中没有了元素,则说明这个 IP 所发出的会话已全部关闭,则可以将该 IP
所对应的 Entry 从 map 中删除了。若 List 中仍有元素,则将变化过的 List 重新再写入回 map。

package com.monkey1024.listener;

import java.util.List;
import java.util.Map;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;

public class MySessionListener implements HttpSessionListener {


    @Override
    public void sessionDestroyed(HttpSessionEvent se) {
        HttpSession currentSession = se.getSession();
        ServletContext sc = currentSession.getServletContext();
        //从ServletContext中获取ipMap
        Map<String,List<HttpSession>> ipMap = (Map<String, List<HttpSession>>) sc.getAttribute("ipMap");
        //从currentSession中获取之前存放的ip
        String ip = (String) currentSession.getAttribute("ip");
        //根据ip去map中找到相应的list
        List<HttpSession> sessionList = ipMap.get(ip);

        //因为已经监听到currentSession被销毁,所以从sessionList中删除currentSession
        sessionList.remove(currentSession);

        //如果list的长度是0,则说明该ip所发出的session对象全部失效,可以将该ip从map中删除了
        //如果list的长度不是0,则说明该ip所发出的session对象还存在,将变化写到map中
        if(sessionList.size() == 0){
            ipMap.remove(ip);
        }else{
            ipMap.put(ip, sessionList);
        }

        //将更新后的map放到ServletContext中
        sc.setAttribute("ipMap", ipMap);
    }


}

4.在web.xml中注册监听器

<listener>
      <listener-class>com.monkey1024.listener.MySessionListener</listener-class>
  </listener>
  <listener>
      <listener-class>com.monkey1024.listener.MyServletContextListener</listener-class>
  </listener>
   <listener>
      <listener-class>com.monkey1024.listener.MyRequestListener</listener-class>
  </listener>

5.创建index.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8" import="com.monkey1024.listener.*"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>

该系统平台目前共有在线用户${ipMap.size() }个。<br>
<a href="/logout">安全退出</a><br>

</body>
</html>

6.创建LogoutServlet
该servlet主要作用是销毁session对象

package com.monkey1024.servlet;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 用户注销
 */
public class LogoutServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //使用sessions销毁
        request.getSession().invalidate();
    }

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }

}