筆者不知道該用哪一個詞來形容ValueStack、ActionContext等能夠在Struts2中用來存放數據的類。這些類使用的範圍不一樣,獲得的方法也不一樣,下面就來一一介紹。html
聲明:本文參考Struts2版本爲2.3.1.2,內容僅供參考,限於筆者水平有限,不免有所疏漏,望您能友善指出。本文發表於ITEYE,謝絕轉載。java
ValueStack在中文版的《Struts2深刻淺出》一書中譯做「值棧」。其自己數據結構是一個棧,使用者能夠把一些對象(又稱做bean)存入值棧中,而後使用動態的表達式來讀取bean的屬性,或者對bean進行一些其餘操做。因爲值棧中可能有多個bean,值棧會按bean出棧的順序依次嘗試使用動態的表達式來讀取值,直到成功讀取值爲止。在Struts2中,默認的值棧實現是OgnlValueStack,即默認使用Ognl這個動態表達式語言來讀取值。apache
在Struts2執行一次請求的過程當中,Struts2會把當前的Action對象自動放入值棧。這樣,在渲染JSP時,JSP裏的代碼使用<s:property value="..."/>之類標籤中的Ognl表達式會直接做用於Action對象,從而方便的讀取Action的屬性。api
如何獲得值棧:瀏覽器
如何將對象存入值棧:安全
讓值棧執行表達式來得到值:服務器
在JSP中跳過棧頂元素直接訪問第二層:session
在JSP中訪問值棧對象自己(而不是它們的屬性)數據結構
總之,值棧主要目的是爲了讓JSP內能方便的訪問Action的屬性。
一些例子:
1 // 此類爲一個封裝數據的簡單類,在下面的例子會用到
2 public class Person {
3
4 private String name;
5
6 public String getName() {
7 return name;
8 }
9
10 public void setName(String name) {
11 this.name = name;
12 }
13 }
1 // 本類將演示攔截器中對值棧的操做
2 public class MyInterceptor extends AbstractInterceptor {
3
4 public String intercept(ActionInvocation invocation) throws Exception {
5 // 得到值棧
6 ValueStack valueStack = invocation.getStack();
7 // 存入值
8 Person person = new Person();
9 valueStack.push(person);
10 // 執行表達式獲取值
11 String name = (String) valueStack.findValue("name");
12 // 其餘代碼
13 return invocation.invoke();
14 }
15 }
1 // 本類將演示在Action中對值棧進行操做
2 public class MyAction extends ActionSupport {
3
4 @Override
5 public String execute() throws Exception {
6 // 得到值棧
7 ValueStack valueStack = ActionContext.getContext().getValueStack();
8 // 存入值
9 Person person = new Person();// 這是以前例子中定義的類
10 valueStack.push(person);
11 // 執行表達式獲取值
12 String name = (String) valueStack.findValue("name");
13 // 其餘代碼
14 // ......
15 return SUCCESS;
16 }
17 // 如下定義的屬性供接下來的JSP例子使用
18 private String message;
19 private Person person;
20 private List<Person> personList;
21
22 public String getMessage() {
23 return message;
24 }
25
26 public Person getPerson() {
27 return person;
28 }
29
30 public List<Person> getPersonList() {
31 return personList;
32 }
33 }
1 <%@page contentType="text/html" pageEncoding="UTF-8"%>
2 <%@taglib uri="/struts-tags" prefix="s" %>
3 <!DOCTYPE html>
4 <html>
5 <head>
6 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
7 <title>JSP Page</title>
8 </head>
9 <body>
10 <!-- 本JSP將演示在JSP中對值棧的使用 -->
11 <!-- 本JSP爲MyAction對應的JSP -->
12
13 <!-- 因爲Action已經被存入的值棧,因此能夠調用Action的屬性 -->
14 <!-- 使用下面的標籤和表達式來顯示MyAction的message屬性 -->
15 <s:property value="message"/>
16 <!-- 使用下面的標籤和表達式來調用Action的getText(...)方法,參數爲MyAction的message屬性 -->
17 <s:property value="getText(message)"/>
18 <!-- 默認狀況下傳遞給cssClass的是字符串常量。可使用「%{}」來啓用Ognl,這樣,傳遞給cssClass的就不是字符串常量"message",而是上面所說的message的值 -->
19 <s:div cssClass="%{message}"/>
20 <!-- 使用s:push標籤來將對象放入值棧,以下 -->
21 <s:push value="person">
22 <!-- 在此s:push標籤內,值棧的棧頂元素爲person,棧頂第二層爲action
23 <!-- 在標籤內直接調用person的屬性(而不是Action的屬性),以下 -->
24 <s:property value="name"/>
25 <!-- 在標籤內也可使用MyAction的屬性,值棧會依次先查找Person是否有該屬性,因爲沒找到,會再MyAction中再查找,以下 -->
26 <s:property value="message"/>
27 <!-- 可使用「[0]」、「[1]」等指定從值棧的哪一層開始查找 -->
28 <!-- 此時,使用「[0]」表示從Person開始查找,固然仍是找不到,值棧就接着到MyAction中查找,以下 -->
29 <s:property value="[0].message"/>
30 <!-- 此時,使用「[1]」將從MyAction開始查找,而跳過了person,以下 -->
31 <s:property value="[1].message"/>
32 <!-- 想要訪問棧頂元素自己使用關鍵字「top」,好比,下面的top就表明棧頂的person,以下 -->
33 <s:property value="top"/>
34 <!-- 或者以下 -->
35 <s:property value="[0].top"/>
36 <!-- 想要訪問MyAction自己的話使用以下寫法 -->
37 <s:property value="[1].top"/>
38 </s:push>
39 <!-- 此時person已被移出值棧,再使用以下標籤和表達式將沒法獲得值 -->
40 <!--<s:property value="name"/>-->
41 <!-- iterator標籤會把list的每一個元素依次存入棧頂,以下 -->
42 <s:iterator value="personList">
43 <!-- 得到List每一個元素中的name屬性 -->
44 <s:property value="name"/>
45 </s:iterator>
46 </body>
47 </html>
ActionContext是Action的上下文,Struts2自動在其中保存了一些在Action執行過程當中所需的對象,好比session, parameters, locale等。Struts2會根據每一個執行HTTP請求的線程來建立對應的ActionContext,即一個線程有一個惟一的ActionContext。所以,使用者可使用靜態方法ActionContext.getContext()來獲取當前線程的ActionContext,也正是因爲這個緣由,使用者不用去操心讓Action是線程安全的。
不管如何,ActionContext都是用來存放數據的。Struts2自己會在其中放入很多數據,而使用者也能夠放入本身想要的數據。ActionContext自己的數據結構是映射結構,即一個Map,用key來映射value。因此使用者徹底能夠像使用Map同樣來使用它,或者直接使用Action.getContextMap()方法來對Map進行操做。
Struts2自己在其中放入的數據有ActionInvocation、application(即ServletContext)、conversionErrors、Locale、action的name、request的參數、HTTP的Session以及值棧等。完整的列表請參考它的Javadoc(本文附錄有對它包含內容的討論)。
因爲ActionContext的線程惟一和靜態方法就能得到的特性,使得在非Action類中能夠直接得到它,而不須要等待Action傳入或注入。須要注意的是,它僅在因爲request而建立的線程中有效(由於request時才建立對應的ActionContext),而在服務器啓動的線程中(好比fliter的init方法)無效。因爲在非Action類中訪問其的方便性,ActionContext也能夠用來在非Action類中向JSP傳遞數據(由於JSP也能很方便的訪問它)。
ValueStack與ActionContext的聯繫和區別:
如何得到ActionContext:
如何向ActionContext中存入值:
如何從ActionContext中讀取值:
總之,在JSP中使用ActionContext一方面是因爲它是映射結構,另外一方面是能讀取Action的一些配置。當你須要爲許多Action提供通用的值的話,可讓每一個Action都提供getXXX()方法,但更好的方法是在攔截器或JSP模板中把這些通用的值存放到ActionContext中(由於攔截器或JSP模板每每通用於多個Action)。
一些例子:
1 // 本類將演示攔截器中對ActionContext的操做
2 public class MyInterceptor extends AbstractInterceptor {
3
4 public String intercept(ActionInvocation invocation) throws Exception {
5 // 得到ActionContext
6 ActionContext actionContext = invocation.getInvocationContext();
7 // 存入值
8 Person person = new Person();
9 actionContext.put("person", person);
10 // 獲取值
11 Object value = actionContext.get("person");
12 // 獲取HttpServletRequest
13 HttpServletRequest request = (HttpServletRequest) actionContext.get(StrutsStatics.HTTP_REQUEST);
14 // 獲取request的Map,即HttpServletRequest.getAttribute(...)和HttpServletRequest.setAttribute(...)所操做的值
15 Map requestMap = (Map) actionContext.get("request");
16 // 其餘代碼
17 // ......
18 return invocation.invoke();
19 }
20 }
1 // 本類將演示在Action中對ActionContext進行操做
2 public class MyAction extends ActionSupport {
3
4 @Override
5 public String execute() throws Exception {
6 // 得到值棧
7 ActionContext actionContext = ActionContext.getContext();
8 // 存入值
9 Person person = new Person();// 這是以前例子中定義的類
10 actionContext.put("person", person);
11 // 獲取值
12 Object object = actionContext.get("person");
13 // 其餘代碼
14 // ......
15 return SUCCESS;
16 }
17 }
1 <!DOCTYPE html>
2 <html>
3 <head>
4 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
5 <title>JSP Page</title>
6 </head>
7 <body>
8 <!-- 本JSP將演示在JSP中對ActionContext的使用 -->
9 <!-- 本JSP爲MyAction對應的JSP -->
10
11 <!-- 因爲Action中已經向ActionContext存入了key爲"person"的值,因此可使用「#person」來獲取它,以下 -->
12 <s:property value="#person"/>
13 <!-- 得到person的name屬性,以下 -->
14 <s:property value="#person.name"/>
15 <!-- 得到Struts2在ActionContext中存入的值,好比request的Map,以下 -->
16 <s:property value="#request"/>
17 <!-- 得到Struts2在ActionContext中存入的值,好比session的Map,以下 -->
18 <s:property value="#session"/>
19 <!-- 得到Struts2在ActionContext中存入的值,request請求傳遞的GET參數或POST參數的Map,以下 -->
20 <s:property value="#parameters"/>
21
22 <!-- 如下演示在JSP中把值存入ActionContext中 -->
23 <!-- 存入一個字符串"myName",key爲"myKey",以下 -->
24 <s:set value="%{'myName'}" var="myKey"/>
25 <!-- 使用s:bean標籤來建立一個對象,並把它存入ActionContext中,key爲myObject,以下 -->
26 <s:bean name="com.example.Person" var="myObject"/>
27 <!-- 以後就能夠用「#」來讀取它們,以下 -->
28 <s:property value="#myKey"/>
29 <s:property value="#myObject"/>
30 </body>
31 </html>
Struts2中提供了兩種對request的操做:一種是Web服務器提供的HttpServletRequest類,這和傳統Java Web項目中的操做request的方式相同;另外一種是一個「request的Map」,即封裝了HttpServletRequest的attributes的映射類,操做該Map至關於操做HttpServletRequest的attributes。之因此提供了Map的操做方式,一是方便操做,二是能方便使用Ognl在JSP標籤中讀取request。不管如何,這兩個request是互通的。至於request的生命週期等概念,與其餘的Java Web項目沒有區別,本文再也不詳述。
使用HttpServletRequest類仍是request的Map
使用request的Map仍是ActionContext:
如何得到HttpServletRequest:
如何得到request的Map:
總之,request仍然符合Java Web網站的通常規律。不過筆者建議使用者應儘可能避免用request傳值。
一些例子:
1 // 本類將演示攔截器中對HttpServletRequest和request的Map的操做
2 public class MyInterceptor extends AbstractInterceptor {
3
4 public String intercept(ActionInvocation invocation) throws Exception {
5 // 得到ActionContext
6 ActionContext actionContext = invocation.getInvocationContext();
7 // 得到HttpServletRequest
8 HttpServletRequest httpServletRequest=(HttpServletRequest)actionContext.get(StrutsStatics.HTTP_REQUEST);
9 // 得到request的Map
10 Map requestMap = (Map) actionContext.get("request");
11 // 建立一個類做爲實例
12 Person person = new Person();
13 // 如下兩行的語句做用相同
14 httpServletRequest.setAttribute("person", person);
15 requestMap.put("person", person);
16 // 其餘代碼
17 // ......
18 return invocation.invoke();
19 }
20 }
1 // 本類將演示在Action中對HttpServletRequest和request的Map進行操做(靜態方法得到ActionContext)
2 public class MyAction extends ActionSupport {
3
4 @Override
5 public String execute() throws Exception {
6 // 得到ActionContext
7 ActionContext actionContext = ActionContext.getContext();
8 // 得到HttpServletRequest
9 HttpServletRequest httpServletRequest=(HttpServletRequest)actionContext.get(StrutsStatics.HTTP_REQUEST);
10 // 得到request的Map
11 Map requestMap = (Map) actionContext.get("request");
12 // 建立一個類做爲實例
13 Person person = new Person();
14 // 如下兩行的語句做用相同
15 httpServletRequest.setAttribute("person", person);
16 requestMap.put("person", person);
17 // 其餘代碼
18 // ......
19 return SUCCESS;
20 }
21 }
1 // 本類將演示在Action中使用ServletRequestAware得到HttpServletRequest(注意:要使用ServletConfigInterceptor攔截器)
2 public class MyAction extends ActionSupport implements ServletRequestAware {
3
4 private HttpServletRequest request;
5
6 //此方法是接口ServletRequestAware的方法
7 public void setServletRequest(HttpServletRequest request) {
8 this.request = request;
9 }
10
11 @Override
12 public String execute() throws Exception {
13 // HttpServletRequest已在該類的字段中準備好,可直接使用
14 // ......
15 return SUCCESS;
16 }
17 }
1 <!DOCTYPE html>
2 <html>
3 <head>
4 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
5 <title>JSP Page</title>
6 </head>
7 <body>
8 <!-- 本JSP將演示在JSP中對request的Map的使用 -->
9 <!-- 本JSP爲MyAction對應的JSP -->
10
11 <!-- request的Map是Struts2自動在ActionContext中存入的值(key爲request),因此使用「#」來訪問ActionContext,從中讀取request -->
12 <s:property value="#request"/>
13 <!-- 如下兩行均是訪問request的Map中key爲「name」的值 -->
14 <s:property value="#request.name"/>
15 <s:property value="#request['name']"/>
16 </body>
17 </html>
1 // 本類將演示在Action中使用ServletRequestAware得到request的Map(注意:要使用ServletConfigInterceptor攔截器)
2 public class MyAction extends ActionSupport implements RequestAware {
3
4 Map<String, Object> request;
5
6 // 該方法是接口RequestAware的方法
7 public void setRequest(Map<String, Object> request) {
8 this.request = request;
9 }
10
11 @Override
12 public String execute() throws Exception {
13 // request的Map已在該類的字段中準備好,可直接使用
14 // ......
15 return SUCCESS;
16 }
17 }
Parameters爲GET或POST等請求時瀏覽器向服務器傳遞而來的參數。在傳統的Java Web項目中,使用HttpServletRequest.getParameter()等方法來獲取參數,而且能夠直接使用HttpServletRequest.getParameterMap()來得到一個封裝了參數的Map。而在Struts2中,Struts2直接把上述Map存放到了ActionContext中,key爲「parameters」。另外,ActionContext還直接提供了ActionContext.getParameters()方法來得到這個Map。所以,在Struts2的各個部件中操做parameters的方法和操做request的Map的方法十分類似,本段再也不詳述。
傳統Java Web項目中的session是咱們都熟悉的,咱們用它來記錄一個用戶的會話狀態。Struts2把HttpServletSession封裝到了一個Map中,即「session的Map」,這相似對request的處理。然而爲了節省系統資源,咱們在不須要session的時候不會建立session。可能正是由於這個緣故,Struts2中沒有把HttpServletSession放入ActionContext中,若是你的程序須要使用HttpServletSession,應該先得到HttpServletRequest,而後使用getSession()或getSession(boolean b)來得到它,同時決定是否須要建立session。對於session的Map,Struts2仍然把它放入了ActionContext中(key爲"session"),可是不要擔憂,這個Map的機制使得只有put新值時纔會建立session。總之,Struts2中對HttpServletSession的操做要先經過HttpServletRequest來得到它,而對session的Map的操做與對request的Map的操做一模一樣,本段再也不詳述。
傳統的Java Web項目中,ServletContext用來存放全局變量,每一個Java虛擬機每一個Web項目只有一個ServletContext。這個ServletContext是由Web服務器建立的,來保證它的惟一性。ServletContext有一些方法能操做它的attributes,這些操做方法和操做一個Map相似。因而,Struts2又來封裝了:它把ServletContext的attributes封裝到了一個Map中,即「application的Map」,而且也放入的ActionContext中(key爲application),所以,對application的Map的操做就若是對request的Map操做,本段再也不詳述。
至於對ServletContext的操做,與HttpServletRequest的操做相似:Struts2將ServletContext放到了 ActionContext中,而且ServletConfigInterceptor提供了對ServletContext的注入接口ServletContextAware。所以,本段再也不詳述。
注意:在Ognl表達式中使用「#application」能夠獲得application的Map,而不是ServletContext。然而在JSP嵌入的Java代碼中(好比「<% application.getAttribute(""); %>」),application爲ServletContext,而不是Map。
用一張表格來總結:
變量 | 從ActionContext中得到 | 生命週期 | 用Ongl來讀取值 | 使用ServletConfigInterceptor來注入 |
ActionContext類 | 靜態方法ActionContext. getContext() | 一次Http請求 | 使用「#」加上key,如「#name」 | 沒法注入 |
ValueStack類 | ActionContext. getValueStack() | 一次Http請求 | 直接填寫來訪問棧中對象的方法,或者使用top來直接得到棧中對象 | 沒法注入 |
HttpServletRequest類 | ActionContext. get( StrutsStatics. HTTP_REQUEST) | 一次Http請求 | 無方便的方法 | 實現ServletRequestAware接口 |
request的Map | ActionContext. get("request") | 一次Http請求 | 使用「#request」再加上key,如「#request.name」或者「#request['name']」 | 實現RequestAware接口 |
parameters的Map | ActionContext. get( "parameters") | 一次Http請求 | 使用「# parameters」再加上key,如「#parameters .name」或者「#parameters ['name']」 | 實現ParameterAware接口 |
HttpServletSession類 | 無(需經過HttpServletRequest來得到) | 一次Http Session會話 | 無方便的方法 | 沒法注入 |
session的Map | ActionContext. get("session") | 每次請求建立,但在一次Http Session會話中數據都是同樣的 | 使用「#session」再加上key,如「# session.name」或者「#session ['name']」 | 實現SessionAware接口 |
ServletContext類 | ActionContext. get( StrutsStatics. SERVLET_CONTEXT) | 網站項目啓動後一直存在且惟一 | 無方便的方法 | 使用ServletContextAware接口 |
application的Map | ActionContext.get( "application") | 每次請求時建立,但其中的數據是網站項目啓動後一直存在且共享 | 使用「# application」再加上key,如「#application .name」或者「#application ['name']」 | 使用ApplicationAware接口 |
附錄1 ActionContext中到底有哪些數據
key | key的聲明處 | value的類型 | value.toString() |
com. opensymphony. xwork2. dispatcher. HttpServletRequest |
StrutsStatics. HTTP_REQUEST | org. apache. struts2. dispatcher. StrutsRequestWrapper | org. apache. struts2. dispatcher. StrutsRequestWrapper @10984e0 |
application | 無 | org. apache. struts2. dispatcher. ApplicationMap | 略 |
com. opensymphony. xwork2. ActionContext. locale | ActionContext. LOCALE | java. util. Locale | zh_CN |
com. opensymphony. xwork2. dispatcher. HttpServletResponse | StrutsStatics. HTTP_RESPONSE | org. apache. catalina. connector. ResponseFacade | org. apache. catalina. connector. ResponseFacade @14ecfe8 |
xwork. NullHandler. createNullObjects |
Boolean | false | |
com. opensymphony. xwork2. ActionContext. name | ActionContext. ACTION_NAME | String | index |
com.opensymphony. xwork2.ActionContext. conversionErrors |
ActionContext. CONVERSION_ERRORS |
java. util. HashMap | {} |
com. opensymphony. xwork2. ActionContext. application | ActionContext. APPLICATION | org. apache. struts2. dispatcher. ApplicationMap | 略 |
attr | 無 | org. apache. struts2. util. AttributeMap | org. apache. struts2. util. AttributeMap @133a2a8 |
com. opensymphony. xwork2. ActionContext. container | ActionContext. CONTAINER | com. opensymphony. xwork2. inject. ContainerImpl | com. opensymphony. xwork2. inject. ContainerImpl @fc02c8 |
com. opensymphony. xwork2. dispatcher. ServletContext | StrutsStatics. SERVLET_CONTEXT | org. apache. catalina. core. ApplicationContextFacade | org. apache. catalina. core. ApplicationContextFacade @11ad78c |
com. opensymphony. xwork2. ActionContext. session | ActionContext. SESSION | org.apache.struts2. dispatcher.SessionMap | {} |
com.opensymphony. xwork2.ActionContext. actionInvocation |
ActionContext. ACTION_INVOCATION | com. opensymphony. xwork2. DefaultActionInvocation | com. opensymphony. xwork2. DefaultActionInvocation @13d4497 |
xwork. MethodAccessor. denyMethodExecution | 筆者很懶,沒有找 | Boolean | false |
report. conversion. errors | 筆者很懶,沒有找 | Boolean | false |
session | 無 | org. apache. struts2. dispatcher. SessionMap | {} |
com. opensymphony. xwork2. util. ValueStack. ValueStack | ValueStack.VALUE_STACK | com. opensymphony. xwork2. ognl. OgnlValueStack | com. opensymphony. xwork2. ognl. OgnlValueStack @16237fd |
request | 無 | org. apache. struts2. dispatcher. RequestMap | 略 |
action | 筆者很懶,沒有找 | com. example. MyAction | 略 |
struts. actionMapping | 筆者很懶,沒有找 | org. apache. struts2. dispatcher. mapper. ActionMapping | org. apache. struts2. dispatcher. mapper. ActionMapping @892cc5 |
parameters | 無 | java. util. HashMap | {} |
com. opensymphony. xwork2. ActionContext. parameters | ActionContext.PARAMETERS | java. util. TreeMap | {} |
注意:該表格爲了排版在某些地方加了空格。
能夠看出,有些相同的對象被以不一樣的key屢次設置到ActionContext中。若是想看看建立ActionContext的源代碼,請看org.apache.struts2.dispatcher.Dispatcher的serviceAction方法和兩個createContextMap方法。
附錄2 Struts2標籤中value屬性直接對ActionContext訪問的問題
經試驗並查看相關源代碼後發現,在使用<s:property value="..."/>時,該標籤的執行類會先根據value中表達式到值棧中執行表達式來查找值。若是在值棧中找到值,就返回該值;若是沒有找到,則把該表達式做爲ActionContext的key,到ActionContext中去找值。好比<s:property value="request"/>也會獲得ActionContext中的request,等價於<s:property value="#request"/>。可是,因爲標籤的執行類會認爲該值時String類型的,而且會直接進行類型轉換。因而,若是直接使用<s:property value="request"/>的話其實會讓頁面拋出異常:Request類型不能轉換成String類型。因此,只能用若是不帶#的話只能成功讀取ActionContext中String類型的值。這種機制使得某些時候棧頂的屬性能夠覆蓋ActionContext中的key,或許你正須要它。然而,鑑於這種機制的不肯定性,筆者建議訪問ActionContext中的數據必定要帶上「#」,能夠免去一些麻煩。
關於這種轉型異常,筆者認爲是Struts2的bug,源代碼以下,當「value = getValue(expr, asType);」時,是考慮了asType的,但從context中讀取時「value = findInContext(expr);」,就沒有考慮asType,而且沒有在其餘地方看到類型檢查操做:
1 // 本代碼截取Struts2.3.1.2版本com.opensymphony.xwork2.ognl.OgnlValueStack類的第340行-352行
2 private Object tryFindValue(String expr, Class asType) throws OgnlException {
3 Object value = null;
4 try {
5 expr = lookupForOverrides(expr);
6 value = getValue(expr, asType);
7 if (value == null) {
8 value = findInContext(expr);
9 }
10 } finally {
11 context.remove(THROW_EXCEPTION_ON_FAILURE);
12 }
13 return value;
14 }