三種獲取 Session 的方式對比
| 方式 | 適用場景 | 優點 | 缺點 |
ServletActionContext.getRequest().getSession() | Action 方法中、攔截器、工具類 | 最直接、功能完整 (可建立/銷毀) | 耦合 Servlet API、難以單元測試 |
實作 SessionAware 接口 | Action 類 (推薦) | 解耦 Servlet API、易測試、Struts2 自動注入 | 僅能 Map 操作、無法直接建立/銷毀 Session |
OGNL #session / <s:property value="%{#session.key}"> | JSP 頁面、結果視圖、標籤屬性 | 頁面直接讀取、支援 EL 風格 | 僅限視圖層、無法寫入複雜邏輯 |
方式一:ServletActionContext (Action 代碼中)
public class UserAction extends ActionSupport {
public String login() {
HttpServletRequest request = ServletActionContext.getRequest();
HttpSession session = request.getSession(); // 若無則建立 (登入場景通常需要)
// 存入用戶資訊
session.setAttribute("loginUser", userService.login(username, password));
session.setAttribute("permissions", permissionService.getPermissions(userId));
return SUCCESS;
}
public String logout() {
HttpSession session = ServletActionContext.getRequest().getSession(false);
if (session != null) {
session.invalidate(); // 徹底銷毀
}
return "login";
}
}
方式二:實作 SessionAware (推薦用於 Action)
public class UserAction extends ActionSupport implements SessionAware {
private Map sessionMap; // Struts2 自動注入 Session Map
@Override
public void setSession(Map session) {
this.sessionMap = session;
}
public String login() {
User user = userService.login(username, password);
if (user != null) {
sessionMap.put("loginUser", user);
sessionMap.put("permissions", permissionService.getPermissions(user.getId()));
return SUCCESS;
}
addFieldError("username", "帳號或密碼錯誤");
return INPUT;
}
public String getUserName() {
User u = (User) sessionMap.get("loginUser");
return u != null ? u.getUsername() : "游客";
}
}
sessionMap 實際是 org.apache.struts2.dispatcher.SessionMap,包裝了 HttpSession,支援 put/remove/get 同步到真實 Session。
- **無法**直接呼叫
invalidate() 或 setMaxInactiveInterval(),需轉型或改用方式一。
- 單元測試時直接傳入
new HashMap() 即可模擬。
方式三:JSP/視圖層使用 #session
<%-- 顯示用戶名 --%>
歡迎,<s:property value="%{#session.loginUser.username}" />
<%-- 迭代權限列表 --%>
<s:iterator value="%{#session.permissions}" var="perm">
<s:property value="#perm.name" /><br/>
</s:iterator>
<%-- 判斷是否登入 --%>
<s:if test="%{#session.loginUser != null}">
<a href="logout.action">登出</a>
</s:if>
<s:else>
<a href="login.jsp">登入</a>
</s:else>
進階:自定義攔截器檢查登入狀態
public class AuthenticationInterceptor extends AbstractInterceptor {
private static final String[] EXCLUDE_PATTERNS = {"/login", "/register", "/captcha", "/static/"};
@Override
public String intercept(ActionInvocation invocation) throws Exception {
ActionContext ctx = invocation.getInvocationContext();
Map session = ctx.getSession(); // 等同 SessionAware 的 Map
String actionName = invocation.getProxy().getActionName();
if (Arrays.stream(EXCLUDE_PATTERNS).anyMatch(actionName::startsWith)) {
return invocation.invoke(); // 白名單放行
}
if (session.get("loginUser") == null) {
// 保存原請求 URL 供登入後跳回
String originalUrl = ServletActionContext.getRequest().getRequestURI();
String query = ServletActionContext.getRequest().getQueryString();
if (query != null) originalUrl += "?" + query;
session.put("savedRequest", originalUrl);
return "loginRedirect"; // struts.xml 定義全局結果
}
return invocation.invoke();
}
}
<!-- struts.xml -->
<interceptors>
<interceptor name="auth" class="com.example.interceptor.AuthenticationInterceptor"/>
<interceptor-stack name="defaultStackWithAuth">
<interceptor-ref name="defaultStack"/>
<interceptor-ref name="auth"/>
</interceptor-stack>
</interceptors>
<default-interceptor-ref name="defaultStackWithAuth"/>
<global-results>
<result name="loginRedirect" type="redirect">/login.jsp</result>
</global-results>
Session 生命週期管理最佳實踐
- 登入時:
request.getSession(true) 建立新 Session,或 request.changeSessionId() 防 Session Fixation。
- 存儲原則:僅存 必要、可序列化、體積小 的物件 (User ID、權限標識、偏好設定)。**嚴禁存放大對象、資料庫連線、非序列化物件**。
- 逾時設定:
web.xml `30` (分鐘)。安全敏感系統建議 15-20 分鐘。
- 登出/超時:
session.invalidate() 徹底銷毀;配合前端定時器提醒用戶即將超時。
- 分叢集:使用 Spring Session + Redis 或 Sticky Session,確保 Session 跨節點共享。
常見錯誤與調試
| 錯誤 | 原因 | 解決 |
| Session 為 null | JSP 頁面 <%@ page session="false"%> 禁用了 Session | 移除該指令或改為 session="true" |
| Session 資料丟失 | Tomcat 重啟、Session 逾時、叢集未同步 | 配置持久化/Redis、調長 timeout、檢查叢集 |
| 併發修改衝突 | 多請求同時寫入 SessionMap | SessionMap 內部同步,極少發生;若發生改用資料庫鎖 |
| 無法取得 ServletAPI | 在非 Action 類 (如 Service) 中使用 ServletActionContext | 傳遞 HttpSession/Map 參數,或改用 ThreadLocal 傳遞 |
總結
Struts2 提供三種 Session 存取途徑:Action 邏輯層首選 SessionAware (解耦、可測),需要完整 ServletAPI 時用 ServletActionContext,視圖層直接用 #session。配合自定義攔截器實現統一認證檢查,遵循「最小權限、可序列化、及時銷毀」原則管理 Session 生命週期。