1. 屬性哪來的
- 當(dāng)我們通過Action處理完用戶請求以后主守,可以直接在頁面中獲取到 action 的屬性值妖混。
- 如果我們在頁面中嘗試遍歷四個域中的屬性少漆,會發(fā)現(xiàn)域中并沒有username之類的Action中的屬性厅须。
- 但是我們自己又沒有在域中進(jìn)行設(shè)置侧馅,經(jīng)過研究發(fā)現(xiàn)request域中出現(xiàn)了一個奇怪屬性
- 屬性的名字:
struts.valueStack
- 屬性的類型:
OgnlValueStack
- 屬性的名字:
-
ValueStack
翻譯過叫做值棧危尿,顧名思義就是存儲值的棧 -
Struts
在每次處理請求都會創(chuàng)建一個新的ValueStack
用來存儲這個請求過程中所要用到的屬性及對象。
像我們熟悉ActionContext
就是值棧的一部分馁痴。
2. ValueStack(值棧)
- 所謂值棧就是一個 OgnlValueStack 類型的對象谊娇,他以struts.valueStack為鍵保存在request域中。Struts2 會將 Action 的實例保存值棧中罗晕。
- 值棧實際是由兩部分組成
Object Stack (CompoundRoot )
和Map Context (Map<String, Object>
)- context是一個Map济欢,里邊保存著三個域赠堵、請求參數(shù)等對應(yīng)的Map對象。
- root 實際就是一個List法褥,Action對象就保存在這個List中茫叭。
- ObjectStack: Struts 把 Action 和相關(guān)對象壓入 ObjectStack 中
- ContextMap: Struts 把各種各樣的映射關(guān)系(一些 Map 類型的對象) 壓入 ContextMap 中. 實際上就是對 ActionContext 的一個引用
- Struts 會把下面這些映射壓入 ContextMap 中
- parameters: 該 Map 中包含當(dāng)前請求的請求參數(shù)
- request: 該 Map 中包含當(dāng)前 request 對象中的所有屬性
- session: 該 Map 中包含當(dāng)前 session 對象中的所有屬性
- application:該 Map 中包含當(dāng)前 application 對象中的所有屬性
- attr: 該 Map 按如下順序來檢索某個屬性: request, session, application
- Struts 會把下面這些映射壓入 ContextMap 中
- ContextMap實際上之前我們就使用過,我們使用的 ActionContext 就包含一個ContextMap
- 我們可以查看一下 CompoundRoot 對象半等,實際上是一個List
-
root (CompoundRoot)就是Struts2存儲Action實例的地方揍愁。當(dāng)我們在JSP中獲取Action中的屬性時,實際上就是去root中進(jìn)行查找杀饵。
- 但是我們查看方法會發(fā)現(xiàn)莽囤,CompoundRoot 每次都是從棧頂取得對象
/**
* A Stack that is implemented using a List.
*
* @author plightbo
* @version $Revision$
*/
public class CompoundRoot extends CopyOnWriteArrayList<Object> {
private static final long serialVersionUID = 8563229069192473995L;
public CompoundRoot() {
}
public CompoundRoot(List<?> list) {
super(list);
}
public CompoundRoot cutStack(int index) {
return new CompoundRoot(subList(index, size()));
}
public Object peek() {
return get(0);
}
public Object pop() {
return remove(0);
}
public void push(Object o) {
add(0, o);
}
}
-
值棧的結(jié)構(gòu)
在Servlet我們屬性HttpServletRequest實際已經(jīng)被Struts2用
org.apache.struts2.dispatcher.StrutsRequestWrapper
給包裝了(裝飾者模式)我們的request的getAttributes方法已經(jīng)被Struts重寫。
-
新的getAttribute方法
- 首先切距,從request域中查找屬性朽缎,如果有直接返回。
- 然后蔚舀,如果域中沒有找到指定屬性饵沧,則去值棧中查找(這里的值棧主要指CompoundRoot),
- 因為值棧中可能有多個對象赌躺,所以會先從索引值為0的對象開始找狼牺,直到找到了需要的屬性,如果找到直接返回礼患。
- 最后是钥,如果值棧中依然沒有找到指定屬性接下來去context中查找,如果找到直接返回缅叠,如果也沒找到則直接返回null悄泥。
3. OGNL (Object-Graph Navigation Language)
- Object-Graph Navigation Language
- 這是一種從Java對象中獲取或設(shè)置屬性的表達(dá)式語言。
- Struts2內(nèi)部使用OGNL表達(dá)式從而大大增強了Struts2的數(shù)據(jù)訪問功能肤粱。
- Struts2 利用 s:property 標(biāo)簽和 OGNL 表達(dá)式來讀取值棧中的屬性值
- 值棧中的屬性值:
- 對于對象棧: 對象棧中某一個對象的屬性值
- Map 棧: request, session, application 的一個屬性值 或 一個請求參數(shù)的值.
- 讀取對象棧中對象的屬性:
- 若想訪問 Object Stack 里的某個對象的屬性. 可以使用以下幾種形式之一:
-
object.propertyName
; -
object['propertyName']
; object["propertyName"]
-
- ObjectStack 里的對象可以通過一個從零開始的下標(biāo)來引用. ObjectStack 里的棧頂對象可以用 [0] 來引用,
- 它下面的那個對象可以用 [1] 引用.
- [0].message
- [n] 的含義是從第 n 個開始搜索, 而不是只搜索第 n 個對象
- 若從棧頂對象開始搜索, 則可以省略下標(biāo)部分: message
- 結(jié)合 s:property 標(biāo)簽:
<s:property value="[0].message" /> <s:property value="message" />
- 默認(rèn)情況下, Action 對象會被 Struts2 自動的放到值棧的棧頂.
- 若想訪問 Object Stack 里的某個對象的屬性. 可以使用以下幾種形式之一:
- 我們可以試著改變值棧的棧頂對象
//改變對象棧弹囚,棧頂?shù)膶ο?ActionContext context = ActionContext.getContext();
ValueStack valueStack = context.getValueStack();
valueStack.push(User.builder().username("豬八戒").address("高老莊").build());
- 獲取對象棧中的數(shù)據(jù):
<!-- 可以直接獲取棧頂對象的屬性,如果棧頂對象沒有就會繼續(xù)往下找领曼,再沒有就從Map棧中找 -->
用戶名 : <s:property value="user.username"></s:property> <br /> <br />
年齡 : <s:property value="age"></s:property> <br /> <br />
地址 : <s:property value="address"></s:property> <br /> <br />
<hr>
<!-- 對于被我們改變的棧頂對象鸥鹉,我們可以用下標(biāo)的方式 -->
用戶名 : <s:property value="[1].username"></s:property> <br /> <br />
地址 : <s:property value="[1].address"></s:property> <br /> <br />
<hr>
<!-- 另外兩種方式 object['filedName'] 或者 object["filedName"] -->
用戶名 : <s:property value="[1]['username']"></s:property> <br /> <br />
地址 : <s:property value="[1]['address']"></s:property> <br /> <br />
- 讀取 Context Map 里的對象的屬性
- 如果希望從map棧中查找屬性,只需要在表達(dá)式的開頭加個#
- 例如:我們要從session域中查找一個屬性 #session.hello
<%
session.setAttribute("hello", "你好");
application.setAttribute("key", "appKey");
session.setAttribute("key", "sessKey");
request.setAttribute("key", "reqKey");
%>
<hr><br><br>
<!-- Map棧中Session數(shù)據(jù) -->
獲取到Session域中屬性: <s:property value="#session.hello"></s:property><br><br>
獲取請求參數(shù): <s:property value="#parameters.username"></s:property><br><br>
<!-- attr 會從小到大的范圍去查找 -->
通過attr獲取屬性值:<s:property value="#attr.key"/>
-
OGNL 調(diào)用字段和方法
- 可以利用 OGNL 調(diào)用
- 任何一個 Java 類里的靜態(tài)字段或方法.
- 被壓入到 ValueStack 棧的對象上的公共字段和方法.
- 首先對于靜態(tài)方法的調(diào)用庶骄,我們需要在 struts.xml 文件中開啟
- 可以利用 OGNL 調(diào)用
<!-- 開啟靜態(tài)方法訪問 -->
<constant name="struts.ognl.allowStaticMethodAccess" value="true"></constant>
- 調(diào)用靜態(tài)字段或方法需要使用如下所示的語法:
- @fullyQualifiedClassName@fieldName: @java.util.Calendar@DECEMBER
- @fullyQualifiedClassName@methodName(argumentList): @app4.Util@now()
<!-- 調(diào)用靜態(tài)方法和字段 -->
調(diào)用靜態(tài)字段: <s:property value="@java.util.Calendar@WEEK_OF_YEAR"></s:property><br>
<!-- 對于調(diào)用靜態(tài)方法毁渗,我們需要去 struts.xml 文件中配置-->
調(diào)用靜態(tài)方法: <s:property value="@org.pan.struts2.entity.User@sayHello('張三豐')"></s:property>
- 調(diào)用 ValueStack 棧的對象的 非靜態(tài)方法和字段(公共字段和方法.)
<!-- 調(diào)用棧頂對象的 公共方法和屬性(屬性就是我們之前訪問的字段值) -->
調(diào)用公共方法: <s:property value="sayYou('巨無霸')"></s:property>
- OGNL 獲取 數(shù)組、List单刁、Map 信息
-
OGNL 訪問數(shù)組類型的屬性
- 有些屬性將返回一個對象數(shù)組而不是單個對象, 可以像讀取任何其他對象屬性那樣讀取它們. 這種數(shù)組型屬性的各個元素以逗號分隔, 并且不帶方括號
- 可以使用下標(biāo)訪問數(shù)組中指定的元素: colors[0]
- 可以通過調(diào)用其 length 字段查出給定數(shù)組中有多少個元素: colors.length
<!-- OGNL 訪問數(shù)組類型的屬性 -->
<%
String[] names = new String[]{"aa","bb","cc","dd"};
request.setAttribute("names", names);
%>
length:<s:property value="#attr.names.length"/>
<br/>
names[1]:<s:property value="#attr.names[1]"/>
-
OGNL 訪問 List 類型屬性
- 有些屬性將返回的類型是 java.util.List, 可以像讀取任何其他屬性那樣讀取它們. 這種 List 的各個元素是字符串, 以逗號分隔, 并且?guī)Х嚼ㄌ?/li>
- 可以使用下標(biāo)訪問 List 中指定的元素: colors[0]
- 可以通過調(diào)用其 size 方法或?qū)S藐P(guān)鍵字 size 的方法查出給定List 的長度: colors.size 或 colors.size()
- 可以通過使用 isEmpty() 方法或專用關(guān)鍵字 isEmpty 來得知給定的 List 是不是空. colors.isEmpty 或 colors.isEmpty()
- 還可以使用 OGNL 表達(dá)式來創(chuàng)建 List, 創(chuàng)建一個 List 與聲明一個 Java 數(shù)組是相同的: {“Red”, “Black”, “Green”}
<%
List<String> colors = new ArrayList<String>();
colors.add("Red");
colors.add("Black");
colors.add("Green");
request.setAttribute("colors", colors);
%>
isEmpty: <s:property value="#request.colors.isEmpty()"></s:property><br><br>
size: <s:property value="#request.colors.size"></s:property><br><br>
colors: <s:property value="#request.colors[1]"></s:property>
-
OGNL 訪問Map 類型屬性
- 讀取一個 Map 類型的屬性將以如下所示的格式返回它所有的鍵值對:
- 若希望檢索出某個 Map 的值, 需要使用如下格式: map['key']
- 可以使用 size 或 size() 得出某個給定的 Map 的鍵值對的個數(shù)
- 可以使用 isEmpty 或 isEmpty() 檢查某給定 Map 是不是空.
- 可以使用 #{key1:value1, key2:value2, key3:value3}的方式創(chuàng)建一個Map
<!-- OGNL 訪問Map類型屬性 -->
<%
Map<String,String> letters = new HashMap<String,String>();
letters.put("AA", "aa");
letters.put("BB", "bb");
letters.put("CC", "cc");
request.setAttribute("letters", letters);
%>
isEmpty: <s:property value="#request.letters.isEmpty"></s:property><br><br>
size:<s:property value="#attr.letters.size()"/><br><br/>
AA:<s:property value="#attr.letters['AA']"/><br><br>
創(chuàng)建Map: <s:set var="testMap" value="#{'AA':'aa', 'BB':'bb', 'CC':'cc'}"></s:set><br><br>
讀取創(chuàng)建的Map: <s:property value="#attr.testMap.AA"></s:property>