| 組件 | 類別 | 職責 |
|---|---|---|
| Listener | MySessionListener | Session 建立/銷毀時維護 Application 級線上用戶 Map |
| Filter | CharacterFilter | 統一設定 UTF-8 編碼、記錄 Client IP 到 Session |
| Filter | LoginFilter | 攔截 /servlet/*, /jsps/*,未登入導向 index.jsp |
| Servlet | LoginServlet | 處理登入,建立 User 存入 Session |
| Servlet | ShowServlet | 查詢 Application 線上用戶 Map,轉發顯示頁 |
| Servlet | KickOutServlet | 管理員踢人:Session.invalidate() + 移除 Map |
| Servlet | LoginOutServlet | 用戶主動登出:Session.invalidate() |
<filter>
<filter-name>character</filter-name>
<filter-class>cn.hncu.filter.CharacterFilter</filter-class>
<init-param><param-name>charset</param-name><param-value>utf-8</param-value></init-param>
</filter>
<filter>
<filter-name>login</filter-name>
<filter-class>cn.hncu.filter.LoginFilter</filter-class>
</filter>
<filter-mapping><filter-name>character</filter-name><url-pattern>/*</url-pattern></filter-mapping>
<filter-mapping>
<filter-name>login</filter-name>
<url-pattern>/servlet/*</url-pattern>
<url-pattern>/jsps/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
</filter-mapping>
<listener><listener-class>cn.hncu.listener.MySessionListener</listener-class></listener>
<servlet><servlet-name>LoginServlet</servlet-name><servlet-class>cn.hncu.servlet.LoginServlet</servlet-class></servlet>
<servlet><servlet-name>ShowServlet</servlet-name><servlet-class>cn.hncu.servlet.ShowServlet</servlet-class></servlet>
<servlet><servlet-name>KickOutServlet</servlet-name><servlet-class>cn.hncu.servlet.KickOutServlet</servlet-class></servlet>
<servlet><servlet-name>LoginOutServlet</servlet-name><servlet-class>cn.hncu.servlet.LoginOutServlet</servlet-class></servlet>
<servlet-mapping><servlet-name>LoginServlet</servlet-name><url-pattern>/LoginServlet</url-pattern></servlet-mapping>
<servlet-mapping><servlet-name>ShowServlet</servlet-name><url-pattern>/servlet/ShowServlet</url-pattern></servlet-mapping>
<servlet-mapping><servlet-name>KickOutServlet</servlet-name><url-pattern>/servlet/KickOutServlet</url-pattern></servlet-mapping>
<servlet-mapping><servlet-name>LoginOutServlet</servlet-name><url-pattern>/servlet/LoginOutServlet</url-pattern></servlet-mapping>
public class MySessionListener implements HttpSessionListener {
@Override
public void sessionCreated(HttpSessionEvent se) {
HttpSession session = se.getSession();
ServletContext ctx = session.getServletContext();
@SuppressWarnings("unchecked")
Map onlines = (Map) ctx.getAttribute("onlines");
if (onlines == null) {
onlines = Collections.synchronizedMap(new HashMap<>()); // 線程安全
ctx.setAttribute("onlines", onlines);
}
onlines.put(session.getId(), session);
}
@Override
public void sessionDestroyed(HttpSessionEvent se) {
HttpSession session = se.getSession();
Map onlines = (Map)
session.getServletContext().getAttribute("onlines");
if (onlines != null) onlines.remove(session.getId());
}
}
public class CharacterFilter implements Filter {
private String charset;
public void init(FilterConfig cfg) { charset = cfg.getInitParameter("charset"); }
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
throws IOException, ServletException {
req.setCharacterEncoding(charset);
resp.setCharacterEncoding(charset);
HttpServletRequest request = (HttpServletRequest) req;
HttpSession session = request.getSession();
if (session.getAttribute("ip") == null) {
session.setAttribute("ip", request.getRemoteAddr()); // 記錄真實 IP
}
chain.doFilter(req, resp);
}
public void destroy() {}
}
public class LoginFilter implements Filter {
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
if (request.getSession().getAttribute("user") == null) {
((HttpServletResponse) resp).sendRedirect(request.getContextPath() + "/index.jsp");
return;
}
chain.doFilter(req, resp);
}
public void init(FilterConfig cfg) {}
public void destroy() {}
}
public class ShowServlet extends HttpServlet {
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
Map onlines = (Map)
req.getServletContext().getAttribute("onlines");
List
public class KickOutServlet extends HttpServlet {
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String id = req.getParameter("id");
Map onlines = (Map)
req.getServletContext().getAttribute("onlines");
if (onlines != null && onlines.containsKey(id)) {
onlines.get(id).invalidate(); // 強制 Session 失效 → 觸發 Listener 移除
}
req.getRequestDispatcher("/servlet/ShowServlet").forward(req, resp);
}
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException { doPost(req, resp); }
}
<%@ taglib prefix="c" uri="https://java.sun.com/jsp/jstl/core" %>
<meta http-equiv="refresh" content="3"> <!-- 3秒自動刷新 -->
<table>
<tr><th>姓名</th><th>上線時間</th><th>最後活動</th><th>IP</th><th>操作</th></tr>
<c:forEach items="${requestScope.onlines}" var="online">
<tr>
<td><c:choose>
<c:when test="${not empty online.user}">${online.user.name}</c:when>
<c:otherwise>訪客</c:otherwise>
</c:choose></td>
<td>${online.createTime}</td>
<td>${online.lastAccessedTime}</td>
<td>${online.ip}</td>
<td>
<c:if test="${online.user != sessionScope.user and not online.user.admin}">
<a href="<c:url value="/servlet/KickOutServlet?id=${online.id}"/>">踢出</a>
</c:if>
</td>
</tr>
</c:forEach>
</table>
public class User {
private String name;
private boolean admin; // true=管理員
// Getter/Setter
}
登入時:name="admin" 設為管理員,擁有踢人權限且不可被踢。
用戶訪問
│
├─ CharacterFilter → 設定編碼、記錄 IP
│
├─ LoginFilter → 檢查 Session.user
│ ├─ 無 → 重導向 index.jsp 登入
│ └─ 有 → 放行
│
├─ LoginServlet → 建立 User 存 Session
│
├─ MySessionListener.sessionCreated → Application.onlines.put(sessionId, session)
│
├─ ShowServlet → 讀取 onlines 映射 → 封裝 List
invalidate() 觸發 Listener 自動清理,無需手動移除 Map。本系統展示了 Java EE 原生 API (Servlet/Filter/Listener) 構建線上用戶管理的經典模式:
架構清晰、零框架依賴,是理解 Java EE Web 核心機制、進階 Spring Security/Session 共享/微服務網關的最佳教具。