Struts 1.x 預設 Action 為單例,多線程共享實例,若 Action 含有成員變量則會發生線程安全問題。雖官方建議「Action 無狀態」,但實際開發中難免需保存請求相關狀態。
利用 Java 反射在 execute 中動態創建當前 Action 子類新實例,委派給 doExecute 處理,實現「每請求一實例」。
public abstract class BaseAction extends Action {
public ActionForward execute(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response)
throws Exception {
// 每次請求創建新實例,隔離線程狀態
BaseAction action = (BaseAction) this.getClass().newInstance();
return action.doExecute(mapping, form, request, response);
}
protected abstract ActionForward doExecute(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response)
throws Exception;
}
// 具體 Action 實現
public class ActionA extends BaseAction {
// 可安全使用成員變量,因為每請求一實例
private String requestId;
protected ActionForward doExecute(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response)
throws Exception {
this.requestId = request.getParameter("id");
// 業務邏輯...
return mapping.findForward("success");
}
}
this.getClass() 獲取實際子類 Class 對象(如 ActionA.class)。newInstance() 呼叫無參構造器創建新對象(需保證子類有 public 無參構造器)。若想像 WebWork 2 透過 ActionContext.getContext() 獲取 Request/Session,可在 BaseAction 中將 ActionMapping、HttpServletRequest 等放入 ThreadLocal,提供靜態輔助類存取:
public class ActionContextHolder {
private static final ThreadLocal ctx = new ThreadLocal<>();
public static void setContext(ActionContext c) { ctx.set(c); }
public static ActionContext getContext() { return ctx.get(); }
public static void clear() { ctx.remove(); }
}
在 BaseAction.execute 開頭設定,finally 清理。
| 優點 | 缺點 |
|---|---|
| 徹底解決線程安全,成員變量隨意用 | 每請求反射創建對象,微小效能開銷(通常可忽略) |
| 代碼結構清晰,符合 OO 直覺 | 需繼承 BaseAction,侵入性稍強 |
| 易於整合 ThreadLocal 上下文模式 | Struts 1.x 設計初衷為單例,違背框架約定 |
此模式在 Struts 1.x 專案中廣泛使用,為遷移至 Struts 2 / Spring MVC(原生每請求一實例)前的過渡期提供了優雅的線程安全解法。