在 Web 應用開發中,統計當前在線用戶人數是一項常見需求。本文將詳細介紹如何利用 Java Servlet 規範提供的三大核心監聽器 —— ServletContextListener、HttpSessionAttributeListener 與 HttpSessionListener —— 協同實現一個輕量級的在線人數統計功能。示例代碼結構清晰,適合初學者參考,也可直接應用於實際項目中。
ServletContextListener.contextInitialized() 在 ServletContext 中創建一個線程安全的 List<String>,用於存放所有在線用戶的用戶名。HttpSessionAttributeListener.attributeAdded() 監聽 Session 屬性新增事件,當檢測到屬性名為 username 時,將該用戶名加入全局列表。HttpSessionListener.sessionDestroyed() 在會話銷毀時,從全局列表中移除對應用戶名。package com.web.servlet;
import java.util.LinkedList;
import java.util.List;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.http.HttpSessionAttributeListener;
import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
/**
* 在線人數統計監聽器
* 同時實現三大監聽器接口,集中管理用戶上下線邏輯
*/
public class OnlineListener implements ServletContextListener,
HttpSessionAttributeListener, HttpSessionListener {
private ServletContext application = null;
@Override
public void contextInitialized(ServletContextEvent sce) {
this.application = sce.getServletContext();
// 初始化線程安全的用戶名列表
this.application.setAttribute("online", new LinkedList<String>());
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
// 應用銷毀時可執行清理工作
}
@Override
public void attributeAdded(HttpSessionBindingEvent event) {
// 只處理 username 屬性的新增
if ("username".equals(event.getName())) {
@SuppressWarnings("unchecked")
List<String> online = (List<String>) application.getAttribute("online");
synchronized (online) {
online.add((String) event.getValue());
application.setAttribute("online", online);
}
}
}
@Override
public void attributeRemoved(HttpSessionBindingEvent event) {
// 可選:處理屬性移除邏輯
}
@Override
public void attributeReplaced(HttpSessionBindingEvent event) {
// 可選:處理屬性替換邏輯
}
@Override
public void sessionCreated(HttpSessionEvent event) {
// 會話創建時不做處理,待登入時再統計
}
@Override
public void sessionDestroyed(HttpSessionEvent event) {
@SuppressWarnings("unchecked")
List<String> online = (List<String>) application.getAttribute("online");
String username = (String) event.getSession().getAttribute("username");
if (username != null) {
synchronized (online) {
online.remove(username);
application.setAttribute("online", online);
}
}
}
}
<!-- 註冊在線統計監聽器 -->
<listener>
<listener-class>com.web.servlet.OnlineListener</listener-class>
</listener>
package com.web.servlet;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class LoginServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
req.setCharacterEncoding("UTF-8");
String username = req.getParameter("username");
if (username != null && !username.trim().isEmpty()) {
// 存入 Session,自動觸發 HttpSessionAttributeListener
req.getSession().setAttribute("username", username);
}
// 從應用上下文獲取在線列表並輸出
@SuppressWarnings("unchecked")
List<String> online = (List<String>) getServletContext().getAttribute("online");
resp.setContentType("text/html;charset=UTF-8");
PrintWriter out = resp.getWriter();
out.println("<html><head><title>用戶列表</title></head><body>");
out.println("當前用戶:" + username + "<hr/><h3>在線用戶列表</h3>");
int size = (online == null) ? 0 : online.size();
for (int i = 0; i < size; i++) {
out.println((i + 1) + ". " + online.get(i) + "<br/>");
}
// 使用 encodeURL 保證會話追蹤
String logoutUrl = resp.encodeURL("logout");
out.println("<hr/><a href='" + logoutUrl + "'>登出</a>");
out.println("</body></html>");
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
doPost(req, resp);
}
}
package com.web.servlet;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class LogoutServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// 銷毀會話,觸發 HttpSessionListener.sessionDestroyed()
req.getSession().invalidate();
@SuppressWarnings("unchecked")
List<String> online = (List<String>) getServletContext().getAttribute("online");
resp.setContentType("text/html;charset=UTF-8");
PrintWriter out = resp.getWriter();
out.println("<html><head><title>用戶列表</title></head><body>");
out.println("<h3>在線用戶列表</h3>");
int size = (online == null) ? 0 : online.size();
for (int i = 0; i < size; i++) {
out.println((i + 1) + ". " + online.get(i) + "<br/>");
}
out.println("<hr/><a href='index.html'>首頁</a></body></html>");
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
doPost(req, resp);
}
}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>用戶登入</title>
</head>
<body>
<form action="login" method="post">
用戶名:<input type="text" name="username" />
<input type="submit" value="登入" />
</form>
</body>
</html>
https://localhost:8080/你的項目名/。ServletContextListener 負責全局容器生命週期,HttpSessionAttributeListener 捕捉登入動作,HttpSessionListener 處理會話結束。LinkedList 並配合 synchronized 保證併發安全。session.invalidate() 而非手動移除屬性,確保監聽器邏輯被正確觸發。response.encodeURL() 支持 Cookie 被禁用場景下的會話追蹤。本示例採用最原生的 Servlet API 實現,無第三方依賴,適合理解 Web 容器監聽機制的基礎教學與輕量級項目直接使用。