ValueStack 是 Struts2 數據傳遞與視圖渲染的核心組件。它不僅是一個對象棧,更結合 OGNL 表達式語言實現了強大的上下文查找機制。本文深入剖析其內部結構、對象存取規則、OGNL 在 JSP/Action/攔截器中的交互細節,並給出常見誤區與最佳實踐。
ArrayList<Object>,棧頂索引 0,push/pop 對應 add(0)/remove(0)。OgnlValueStack,集成 OGNL 引擎解析表達式。| 階段 | 入棧對象 | 來源 | 位置 |
|---|---|---|---|
| 1. Action 創建後 | Action 實例 | 框架自動 | 棧底 (最早) |
| 2. ModelDriven 攔截器 | getModel() 返回對象 | ModelDrivenInterceptor | Action 之上 |
| 3. 自定義攔截器 | 任意對象 | stack.push(obj) | 當前棧頂 |
| 4. JSP 標簽 | 迭代變量、<s:push> 值 | <s:iterator>, <s:push> | 標簽體內臨時,結束自動 pop |
關鍵點:同一請求中,Action 永遠在棧底;越晚入棧的對象優先級越高(OGNL 查找順序)。
對於表達式 property,ValueStack 依次檢查棧中對象(從頂向底):
Map,嘗試 map.get("property")。getProperty() / isProperty()。[0])。OgnlException 或返回 null (視標簽屬性)。繼承鏈查找:OGNL 會沿著對象繼承鏈查找 getter,直到 Object。
| 語法 | 含義 | 示例 |
|---|---|---|
property | 從棧頂開始查找 (等價 [0].property) | <s:property value="name"/> |
[n].property | 從第 n 層 (0-based) 開始查找,跳過上層 | <s:property value="[1].name"/> 訪問 Action 的 name |
top.property | 訪問棧頂對象本身的屬性 (同 [0].property) | <s:property value="top.name"/> |
[n].top.property | 訪問第 n 層對象本身 | <s:property value="[1].top.name"/> |
#attr.property | 訪問 Page/Request/Session/Application 作用域屬性 | <s:property value="#request.msg"/> |
%{expr} | 強制標簽屬性按 OGNL 解析 (非字符串字面量) | <s:set var="x" value="%{1+2}"/> |
public class MyAction extends ActionSupport {
public String execute() {
ValueStack stack = ActionContext.getContext().getValueStack();
// 存入臨時對象,供 JSP 使用
stack.push(new User("temp", 99));
// 設置值 (作用於棧頂對象,若無 setter 則創建 Map 存儲)
stack.setValue("tempKey", "tempValue");
// 獲取值
String val = (String) stack.findValue("tempKey");
return SUCCESS;
}
}
public class MyInterceptor extends AbstractInterceptor {
@Override
public String intercept(ActionInvocation invocation) throws Exception {
ValueStack stack = invocation.getStack();
// 在 Action 執行前修改參數
stack.setValue("preProcessed", true);
return invocation.invoke();
}
}
<!-- iterator 將當前元素 push 到棧頂 -->
<s:iterator value="userList" var="u" status="st">
<s:property value="name"/> <!-- 直接訪問 User.name -->
<s:property value="[1].actionProp"/> <!-- 訪問 Action (棧第2層) 的屬性 -->
<s:push value="address">
<s:property value="city"/> <!-- 訪問 Address.city -->
</s:push>
</s:iterator>
[n].prop 指定層級,或重命名屬性。<s:property value="obj.prop"/> 不報錯但無輸出:OGNL 查找失敗返回 null,標簽默認不拋異常。排查:檢查 getter 名稱、對象是否入棧、類型轉換錯誤 (OGNL 日誌開啟 DEBUG)。[1].prop。ActionContext.getContext() 基於 ThreadLocal,單請求單線程安全;切勿 在單例 Bean/靜態字段中持有 ValueStack 引用跨請求使用。#request.bigData 或流式輸出。OgnlValueStack 繼承 CompoundRoot (ArrayList) 實現 ValueStack 接口。findValue(expr) 內部調用 Ognl.getValue(expr, root, context),root 即棧自身 (實現 Map 接口,get(key) 觸發上述查找算法)。setValue(expr, value) 僅作用於棧頂對象 (或創建 CompoundRoot 內部 Map 存儲)。ActionContextCleanUp 監聽器清理 ThreadLocal,棧對象可被 GC。ValueStack + OGNL 是 Struts2 「約定優於配置」的基石:Action 屬性自動入棧,JSP 直接用屬性名訪問,無需繁瑣的 request.setAttribute。掌握 棧順序、查找算法、層級選擇語法,即可駕馭 90% 的數據顯示場景,避免「為什麼取不到值」的困擾。