上一篇
在Java Web应用中,查看在线用户通常通过HttpSessionListener监听会话状态实现,核心步骤:创建自定义监听器跟踪session创建/销毁,使用线程安全集合(如ConcurrentHashMap)存储
在线用户信息,在ServletContext中维护实时用户列表,可通过获取ServletContext中的用户集合实时展示在线状态。
核心实现方案
HttpSessionListener 监听会话状态
通过监听会话创建/销毁事件自动更新在线用户列表。
// 创建监听器
public class OnlineUserListener implements HttpSessionListener {
private static final Set<String> onlineUsers = Collections.synchronizedSet(new HashSet<>());
@Override
public void sessionCreated(HttpSessionEvent se) {
String userId = (String) se.getSession().getAttribute("userId");
if (userId != null) onlineUsers.add(userId);
}
@Override
public void sessionDestroyed(HttpSessionEvent se) {
String userId = (String) se.getSession().getAttribute("userId");
onlineUsers.remove(userId);
}
public static Set<String> getOnlineUsers() {
return new HashSet<>(onlineUsers);
}
}
注册监听器(web.xml):
<listener>
<listener-class>com.example.OnlineUserListener</listener-class>
</listener>
优点:实时性强,自动管理会话生命周期
缺点:无法捕获用户非正常退出(如直接关闭浏览器)
Filter + 心跳检测(推荐)
通过用户活动时间戳判断活跃状态,避免会话超时误判。

// 心跳过滤器
public class SessionActivityFilter implements Filter {
private static final Map<String, Long> userLastActivity = new ConcurrentHashMap<>();
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
String userId = (String) request.getSession().getAttribute("userId");
if (userId != null) {
userLastActivity.put(userId, System.currentTimeMillis()); // 更新活动时间
}
chain.doFilter(req, res);
}
// 获取活跃用户(5分钟内活动)
public static Set<String> getActiveUsers() {
long now = System.currentTimeMillis();
return userLastActivity.entrySet().stream()
.filter(entry -> now - entry.getValue() < 300_000) // 5分钟
.map(Map.Entry::getKey)
.collect(Collectors.toSet());
}
}
配置过滤路径(web.xml):
<filter>
<filter-name>sessionActivityFilter</filter-name>
<filter-class>com.example.SessionActivityFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>sessionActivityFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
结合Redis存储分布式状态
适用于集群环境,解决多服务器间状态同步问题。
// 使用Redis记录活动时间
try (Jedis jedis = jedisPool.getResource()) {
String userId = getCurrentUserId();
jedis.setex("user:activity:" + userId, 300, String.valueOf(System.currentTimeMillis())); // 5分钟过期
}
// 查询在线用户
Set<String> onlineUsers = jedis.keys("user:activity:*").stream()
.map(key -> key.split(":")[2])
.collect(Collectors.toSet());
优势:支持水平扩展,数据持久化
工具建议:Spring Data Redis、Lettuce

在线用户信息增强
可扩展存储更多会话数据:
public class UserSession {
private String userId;
private String ipAddress;
private LocalDateTime loginTime;
// 存储到ConcurrentHashMap或Redis
}
public static Map<String, UserSession> activeSessions = new ConcurrentHashMap<>();
安全性及性能优化
- 隐私保护
- 脱敏处理:仅展示部分用户ID(如
user***123) - 权限控制:限制管理员访问
/admin/online-users
- 脱敏处理:仅展示部分用户ID(如
- 性能提升
- 缓存查询结果:使用Guava Cache定时刷新
LoadingCache<String, Set<String>> onlineUsersCache = CacheBuilder.newBuilder() .refreshAfterWrite(1, TimeUnit.MINUTES) // 自动刷新 .build(this::loadActiveUsersFromRedis);
- 缓存查询结果:使用Guava Cache定时刷新
- 会话可靠性
- 配置web.xml确保会话超时时间:
<session-config> <session-timeout>30</session-timeout> <!-- 30分钟 --> </session-config>
- 配置web.xml确保会话超时时间:
方案选型指南
| 场景 | 推荐方案 | 特点 |
|---|---|---|
| 单机应用 | HttpSessionListener | 简单易用,零额外依赖 |
| 高精度活跃检测 | Filter + 时间戳 | 避免会话超时误差 |
| 微服务/集群 | Redis存储 | 分布式支持,高可用 |
| 需要详细会话数据 | 扩展UserSession对象 | 记录IP、设备等元信息 |
关键注意事项
- 会话固定攻击防护
用户登录后重置Session ID:request.getSession().invalidate(); HttpSession newSession = request.getSession(true);
- 内存泄漏预防
使用WeakReference存储会话数据,或在sessionDestroyed()中显式清除引用 - 跨平台兼容
对于WebSocket连接,可通过@OnOpen/@OnClose注解管理状态
最佳实践:生产环境建议采用 Filter+Redis 组合方案,平衡实时性、扩展性和资源开销,通过Spring Boot Actuator可集成监控端点,实时查看在线用户数据。
引用说明:本文代码实现参考Oracle官方HttpSessionListener文档,Redis会话管理方案基于Spring Session最佳实践,安全建议依据OWASP会话管理标准。

