Struts2 核心設計目標之一是Action 與 Servlet API 解耦,使 Action 成為純 POJO,便於單元測試。但實際開發中,往往不可避免地需要訪問 HttpServletRequest、HttpServletResponse、HttpSession、ServletContext。Struts2 提供三種機制滿足需求,本文深度對比其原理、優劣與最佳實踐。
| 需訪問對象 | 接口 | 注入方法 | 參數類型 |
|---|---|---|---|
| HttpServletRequest | ServletRequestAware | setServletRequest() | HttpServletRequest |
| HttpServletResponse | ServletResponseAware | setServletResponse() | HttpServletResponse |
| HttpSession | SessionAware | setSession() | Map<String, Object> (Session Map) |
| ServletContext | ApplicationAware | setApplication() | Map<String, Object> (Application Map) |
| Request 參數 Map | ParameterAware | setParameters() | Map<String, String[]> |
| Request 屬性 Map | RequestAware | setRequest() | Map<String, Object> (Request Map) |
public class UserAction extends ActionSupport
implements ServletRequestAware, ServletResponseAware, SessionAware, ApplicationAware {
private HttpServletRequest request;
private HttpServletResponse response;
private Map<String, Object> sessionMap;
private Map<String, Object> applicationMap;
// Struts2 容器自動調用注入
@Override public void setServletRequest(HttpServletRequest request) { this.request = request; }
@Override public void setServletResponse(HttpServletResponse response) { this.response = response; }
@Override public void setSession(Map<String, Object> session) { this.sessionMap = session; }
@Override public void setApplication(Map<String, Object> application) { this.applicationMap = application; }
public String login() {
// 1. 直接使用原生 Request/Response
String ip = request.getRemoteAddr();
response.setHeader("X-Custom-Header", "Struts2");
// 2. 使用 Map 操作 Session/Application(無需強轉,線程安全)
User user = userService.authenticate(getUsername(), getPassword());
if (user != null) {
sessionMap.put("LOGIN_USER", user); // 等同 session.setAttribute
applicationMap.put("ONLINE_COUNT", getOnlineCount() + 1);
return SUCCESS;
}
return ERROR;
}
}
Struts2 默認攔截器棧包含:
servletConfig:注入 Request/Response(實現 ServletRequestAware/ResponseAware)sessionAutowiring / applicationAutowiring:創建 SessionMap/ApplicationMap 代理,注入 SessionAware/ApplicationAware優點:
✅ Action 保持 POJO,無靜態依賴,單元測試極簡單(直接 new Action 再 setXxx)。
✅ Session/Application 以 Map 形式暴露,隱藏 Servlet API 細節,增強可測試性。
✅ 類型安全,IDE 提示友好。
缺點:
❌ 需實現多個接口,代碼略顯冗長。
❌ 注入發生在 Action 實例化後、execute() 前,構造器/字段初始化階段無法使用注入對象。
// 獲取原生 Servlet API 對象
HttpServletRequest request = ServletActionContext.getRequest();
HttpServletResponse response = ServletActionContext.getResponse();
HttpSession session = ServletActionContext.getRequest().getSession();
ServletContext application = ServletActionContext.getServletContext();
// 獲取 ActionContext(見方式三)
ActionContext ctx = ServletActionContext.getActionContext();
ServletActionContext 內部維護 ThreadLocal<ActionContext>,在請求處理線程中綁定當前 ActionContext,其中持有 Request/Response 等對象。
| 優點 | 缺點 |
|---|---|
|
|
適用場景:非 Action 類(工具類、攔截器、過濾器)臨時獲取 Request/Response;遺留代碼快速遷移;原型驗證。核心業務 Action 禁用。
ActionContext ctx = ActionContext.getContext(); // 獲取當前線程上下文
// 1. 獲取/設置 Request 參數 (Map)
Map<String, String[]> params = ctx.getParameters();
params.put("newParam", new String[]{"value"}); // 設置參數
// 2. 獲取/設置 Request 屬性 (Map)
Map<String, Object> requestMap = (Map) ctx.get("request"); // 或 ctx.getRequest()
// 實際推薦:實現 RequestAware 獲取 Map
// 3. 獲取/設置 Session (Map)
Map<String, Object> sessionMap = ctx.getSession();
sessionMap.put("key", value); // 等同 session.setAttribute
// 4. 獲取/設置 Application (Map)
Map<String, Object> appMap = ctx.getApplication();
appMap.put("key", value); // 等同 application.setAttribute
getParameters() 返回參數 Map,可直接修改參數值(影響后續類型轉換)。| 維度 | Aware 接口 (推薦) | ServletActionContext | ActionContext |
|---|---|---|---|
| 解耦程度 | ⭐⭐⭐⭐⭐ (完全 POJO) | ⭐ (強耦合 Servlet) | ⭐⭐⭐⭐ (Map 視圖) |
| 可測試性 | ⭐⭐⭐⭐⭐ (直接 setXxx) | ⭐ (需模擬 ThreadLocal) | ⭐⭐⭐ (構造 Map 注入) |
| 功能完整性 | ⭐⭐⭐⭐ (Map 限制部分操作) | ⭐⭐⭐⭐⭐ (原生 API) | ⭐⭐⭐ (Map 視圖) |
| 代碼侵入性 | 實現多接口 | 零侵入 (靜態調用) | 低 (獲取上下文) |
| 適用範圍 | 所有 Action | 非 Action 類/攔截器/工具類 | 攔截器/結果類/標簽/Action |
| 線程安全 | 安全 (請求作用域實例) | ThreadLocal 風險 | 安全 (請求作用域 Map) |
ServletRequestAware、SessionAware 等,保證可測試性、解耦性。prepare() 前、execute() 前,構造器不可用。sessionMap.put(key, val) 而非 session.setAttribute(key, val),保持解耦。
// struts-default.xml 定義的攔截器棧片段
<interceptor-stack name="defaultStack">
<interceptor-ref name="servletConfig"/> <!-- 注入 Request/Response -->
<interceptor-ref name="prepare"/>
<interceptor-ref name="checkbox"/>
<interceptor-ref name="params"/> <!-- 參數注入 -->
<interceptor-ref name="conversionError"/>
<interceptor-ref name="sessionAutowiring"/> <!-- 創建 SessionMap 並注入 SessionAware -->
<interceptor-ref name="applicationAutowiring"/> <!-- 創建 ApplicationMap 並注入 ApplicationAware -->
...
</interceptor-stack>
// ServletConfigInterceptor 核心邏輯
public class ServletConfigInterceptor extends AbstractInterceptor {
public String intercept(ActionInvocation invocation) {
Action action = invocation.getAction();
if (action instanceof ServletRequestAware) {
((ServletRequestAware) action).setServletRequest(ServletActionContext.getRequest());
}
if (action instanceof ServletResponseAware) {
((ServletResponseAware) action).setServletResponse(ServletActionContext.getResponse());
}
if (action instanceof ParameterAware) {
((ParameterAware) action).setParameters(ServletActionContext.getRequest().getParameterMap());
}
return invocation.invoke();
}
}
Struts2 提供三種訪問 Servlet API 的方式,本質是解耦與便利的權衡。Aware 接口是正統、推薦、可測試的選擇;ServletActionContext 是打破規則的緊急通道;ActionContext 是屏蔽 Servlet API 的統一 Map 視圖。架構師應制定規約:Action 層強制 Aware,非 Action 層酌情静態,統一屬性操作用 Map,方能構建可維護、可測試的 Struts2 應用。