設計目標與技術優勢
- MVC 徹底分離:Action 為純 POJO,無 Servlet API 耦合,易測試、易復用。
- 攔截器鏈:橫切關注點 (日誌、驗證、事務、文件上傳、I18n) 插件化組裝。
- OGNL 表達式語言:強大的屬性存取、集合投影、類型轉換,實現「值棧」數據流動。
- 插件化架構:REST、JSON、JFreeChart、Tiles、Spring、Hibernate 等無縫整合。
請求處理全流程 (核心類職責)
HTTP Request
│
▼
┌─────────────────────────────────────┐
│ 1. FilterDispatcher (核心控制器) │
│ ├─ 初始化 DispatcherUtils │
│ ├─ ActionMapper 決定是否映射 Action│
│ ├─ ActionProxyFactory 創建 ActionProxy│
│ └─ ActionProxy.execute() 觸發調用 │
└─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ 2. ActionProxy (代理) │
│ ├─ ConfigurationManager 讀 struts.xml│
│ ├─ 創建 ActionInvocation │
│ └─ 調用 ActionInvocation.invoke() │
└─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ 3. ActionInvocation (調度核心) │
│ ├─ 按配置順序調用 Interceptor │
│ ├─ 前置攔截器 (pre) │
│ ├─ 調用 Action.execute() │
│ ├─ 後置攔截器 (post) │
│ └─ 根據 Result 類型渲染視圖 │
└─────────────────────────────────────┘
│
▼
HTTP Response
核心組件深度剖析
1. FilterDispatcher (struts2-core 中 FilterDispatcher 類)
- 實現
javax.servlet.Filter,web.xml 配置為 /*。
doFilter() 關鍵邏輯:
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
DispatcherUtils du = DispatcherUtils.getInstance();
du.prepare(request, response); // locale、encoding、multipart 處理
ActionMapper mapper = ActionMapperFactory.getMapper();
ActionMapping mapping = mapper.getMapping(request); // 解析 namespace/actionName
if (mapping == null) { // 靜態資源或非 Struts 請求
chain.doFilter(request, response);
return;
}
// 核心調用鏈
ActionProxy proxy = ActionProxyFactory.getFactory().createActionProxy(
mapping.getNamespace(), mapping.getName(), extraContext);
proxy.execute(); // 觸發 ActionInvocation
}
2. ActionInvocation (DefaultActionInvocation 實現)
- 持有
List<Interceptor> interceptors、ActionProxy proxy、ValueStack stack。
invoke() 遞歸調用攔截器鏈:
public String invoke() throws Exception {
if (interceptors.hasNext()) {
InterceptorMapping im = interceptors.next();
Interceptor interceptor = im.getInterceptor();
return interceptor.intercept(this); // 責任鏈模式
}
// 所有攔截器執行完,執行 Action
return proxy.execute(); // 實際調用 Action 方法
}
攔截器在 intercept(ActionInvocation invocation) 中決定是否 invocation.invoke() 繼續向後,實現前置/後置邏輯。
3. 內建攔截器清單 (struts-default.xml 節選)
| 名稱 | 類 | 功能 |
| params | ParametersInterceptor | 請求參數注入 Action 屬性 (OGNL) |
| validation | AnnotationValidationInterceptor | JSR-303/註解驗證 |
| workflow | DefaultWorkflowInterceptor | 驗證失敗自動返回 input 結果 |
| fileUpload | FileUploadInterceptor | 多部分文件上傳處理 |
| i18n | I18nInterceptor | 區域語言切換 |
| servletConfig | ServletConfigInterceptor | 注入 ServletRequest/Response/Session |
| prepare | PrepareInterceptor | 調用 Preparable.prepare() 預處理 |
| modelDriven | ModelDrivenInterceptor | ModelDriven 介面模型驅動 |
| token | TokenInterceptor | 表單重複提交防護 |
| timer | TimerInterceptor | Action 執行耗時統計 |
| exception | ExceptionMappingInterceptor | 異常映射到結果頁 |
4. 自定義攔截器實現
public class AuditInterceptor implements Interceptor {
private static final Logger LOG = LoggerFactory.getLogger(AuditInterceptor.class);
@Override public void init() {}
@Override public void destroy() {}
@Override
public String intercept(ActionInvocation invocation) throws Exception {
String actionName = invocation.getProxy().getActionName();
long start = System.currentTimeMillis();
try {
return invocation.invoke(); // 繼續鏈
} finally {
long cost = System.currentTimeMillis() - start;
LOG.info("Action [{}] executed in {} ms", actionName, cost);
}
}
}
註冊使用:
<package name="default" extends="struts-default">
<interceptors>
<interceptor name="audit" class="com.example.AuditInterceptor"/>
<interceptor-stack name="myStack">
<interceptor-ref name="defaultStack"/> <!-- 包含 params, validation, workflow 等 -->
<interceptor-ref name="audit"/>
</interceptor-stack>
</interceptors>
<default-interceptor-ref name="myStack"/>
<action name="User_*" class="com.example.UserAction" method="{1}">
<result>/user/{1}.jsp</result>
</action>
</package>
5. 結果類型與視圖渲染
| Result Type | 類 | 用途 |
| dispatcher (預設) | ServletDispatcherResult | JSP/HTML 轉發 |
| redirect | ServletRedirectResult | HTTP 302 重定向 |
| redirectAction | ServletActionRedirectResult | 重定向到另一 Action |
| json | JSONResult (struts2-json-plugin) | Action 屬性序列化為 JSON |
| stream | StreamResult | 文件下載、圖片輸出、二進制流 |
| freemarker/velocity | FreeMarkerResult/VelocityResult | 模板引擎渲染 |
值棧 與 OGNL 表達式
- ValueStack:線程局部棧 (
ThreadLocal<ValueStack>),存放 Action 實例、Model、請求參數、Session/Application 屬性。
- OGNL 表達式在 JSP/FTL 中存取:
<s:property value="user.name"/>、<s:iterator value="orders.{? #this.status=='PAID'}"/> (投影過濾)。
<s:form>、<s:textfield name="user.email"/> 自動綁定到 Action 的 getUser().setEmail()。
常見問題與調優
- OGNL 安全漏洞:嚴禁直接執行用戶輸入表達式,升級至 2.5.30+,配置
struts.ognl.enableOGNLCaching=true、struts.ognl.allowStaticMethodAccess=false。
- 多文件上傳記憶體溢出:調整
struts.multipart.maxSize、struts.multipart.saveDir、使用流式處理。
- 攔截器順序錯誤:
params 必須在 validation 之前,否則屬性未注入驗證失效。
- Action 非線程安全:Struts 2 預設每請求創建新 Action 實例 (prototype),避免併發問題;若配置 Spring 單例需自行保證線程安全。
總結
Struts 2 以 FilterDispatcher 入口 → ActionProxy 代理 → ActionInvocation 責任鏈 → Interceptor 橫切 → OGNL 值棧 → Result 視圖 為核心流程。掌握攔截器鏈組裝、OGNL 表達式、Result 類型選擇,即可駕馭企業級 Web 應用開發。
- 上一篇文章: dom4j 實戰全書:XML 創建、解析、修改、XPath 查詢與命名空間處理
- 下一篇文章: 常見 HTTP 狀態碼速查表:從 200 到 500 的核心代碼解析
Copyright ©
程式碼之家 All Rights Reserved