2009年5月13日 星期三

[Struts2] Struts2 Control Tags

先前我們已經看過了 Struts2 framework 所提供的 Data tags,主要針對資料在 ActionContext 與頁面之間的移動。在這裡我們將要看到的是 Control tags,顧名思義就與控制流程相關的 tags。官方網站中列出了 9 種的 Control tags,不過在這裡我只會介紹其中的 3 種:1) iterator, 2) if 與 3) else tag。

iterator tags
iterator 對於你來說應該不陌生,這個顧名思義就是用來進行迭代的 tag。iterator tag 可以針對 Collection, Map, Enumeration, Iterator 與 array 進行迭代,並且提供目前迭代的 status 讓 programmer 可以針對狀況進行特殊行為,而此 status 會儲存在 ActionContext 中讓 programmer 呼叫使用。例如 programmer 可以根據目前迭代的 status 讓 Collection 的顯示出現單數與奇數列的資料有不同的背景顏色等等功能。下面的範例中示範了所有 iterator tag 與 status 的狀態:
<s:iterator value="list" status="listStatus">
Count:<s:property value="#listStatus.count"/><br />
Index:<s:property value="#listStatus.index"/><br />
isEven:<s:property value="#listStatus.even"/><br />
isOdd:<s:property value="#listStatus.odd"/><br />
isFirst:<s:property value="#listStatus.first"/><br />
isLast:<s:property value="#listStatus.last"/><br />
Value:<s:property/><br />
<hr />
</s:iterator>

iterator tag 透過 value attribute 取得該集合,我們並且指定將目前迭代的 status 儲存為 listStatus 變數,並且放置於 ActionContext。而 iterator status 有哪些狀態可以顯示呢?我們可以查詢 IteratorStatus class,因為這是 status 的實際物件。
上面的範例中,我們的集合內儲存的是 String 物件,所以迭代時無須指定 property,如果你的集合內儲存的是某個 JavaBean 物件,那每一次迭代的 property 就直接呼叫該 property 即可!

if, elseif and else tags

if, elseif 與 else tags 其實也很簡單,其目的就像是我們在程式語言中的 if-elseif- else 語句一樣,話不多說,直接給個範例:
<s:if test="age > 10">You are old</s:if>
<s:elseif test="age < 10">You are young</s:elseif>
<s:else>You are 10 years old</s:else>

我們在 test attribute 中利用 OGNL expression 進行條件測試,OGNL 跟 EL 一樣有提供 boolean operation 的功能。上面的範例就是測試 age property。

在這裡我們簡介了兩個 Struts2 的 Control tags,這裡的 tags 有些用法跟 JavaServer Page Standard Tag Library(JSTL) 其實是很類似的!所以有寫過 JSTL 的人應該會感到很熟悉也很容易上手。

2009年5月12日 星期二

[Struts2] Struts2 Data Tags

在之前的文章裡,我們已經看過了 Action, Interceptor 與 OGNL 等。這些主要是整個 Struts2 framework 中的核心 components,而且在 MVC 中主要 focus 在 Model 與 Controller 的角色。現在我們要切換 MVC 角色中不可或缺,對於使用者來說是很重要的 View 部份!
Struts 2 framework 中提供了很多的 tags 可以使用,我將這些 tags 中我們在這裡先介紹跟 data 相關的部份,而所謂跟 data 相關是指這些 tags 讓我們可以很方便的將資料由 ValueStack 中搬進與搬出,以下將簡介 5 個重要的 data tags:1) property tag, 2) set tag, 3) push tag, 4) bean tag 與 5) action tag。
在進入正題之前,我還是要提醒一下,要使用 Struts2 framework 的 tags 之前,我們的 JSP 頁面要先撰寫 tag directive 告知 JSP 我們要使用 Struts2 tags:
<%@ taglib uri="/struts-tags" prefix="s" %>


property tag
property tag 在之前我們已經使用過很多次了,而這個 tag 應該是整個 Struts2 framework 中最常使用到的 tag 之一。property tag 提供了一種很快速很方便的機制,讓 programmer 可以很容易的將資料由 ActionContext 中移動,而資料的移動是根據 OGNL 中撰寫的 expression 來決定,資料也會根據 OGNL 的 Type Converter 進行轉換型態。不過有一點要注意的是,如果 OGNL 指到的 Java type object 沒有對應的 converter 的話,OGNL 預設是呼叫該物件的 toString() method。
關於 property tag 中的相關設定請參考 Struts2 官方網站,在此就不贅述了!
以下是一個簡單的使用 property tag 範例:
<s:property value="user.age" />

上述的 tag 中我們撰寫 OGNL 要求將 ValueStack 中的 user.age 取出,並且轉換成 String 顯示!

set tag
對於 set 來說,意義在於將某個物件給予另一個新的名稱,有一些理由讓這個 tag 存在,例如我們如果用一個 OGNL expression 來取出資料時,expression 很長很容易撰寫錯誤時,這個 tag 就可以適時的發揮功用,減輕 programmer 的一些負擔。因為 set tag 可以將一些複雜又常用到的 OGNL 先給予新的 reference,而 programmer 在 set tag 之後就可以根據新的 reference 撰寫相對簡結的 OGNL expression。而且我們可以新的 reference 指定在 ActionContext 中的不同位置,這也可以將 data 複製到新的位置!透過 set tag 的 scope attribute 我們可以指定新的 data 要被存放在 JSP 中就有的 scope (application, session, request, and page) 或是 Struts2 中特定的 action scope。
其餘的 attribute 在這。以下就給個範例,不過我們沒有複雜的 OGNL:
<s:set name="myUser" value="user"></s:set>
Your Name:<s:property value="#myUser.name"/>

我們將 ValueStack 的 user 物件重新存放,並且給予 myUser 的變數名稱,因為我們的 scope 是採用預設的 action,所以之後的 OGNL 要使用的話,就必須以 # 開頭!在之前的深入探討 OGNL 中我們有提到,如果要在 OGNL 中取得 session 的 data 就必須是 #session 開頭,在這裡有去的是,如果 set tag 將新的 data 除存在 action scope 的話,意思是 myUser 物件與 session 是相同的高度,也就是 ActionContext 中的最上層!不過要注意的是,如果我們將 data 放至於 action scope,其生命週期跟 request 是一樣的喔!如果要更長的生命週期,那就選擇使用 session 或 application 囉~

push tag
set tag 讓你可以將 data 建立一份副本,而且可以設定放置在哪個 scope;而 push tag 也有類似的功能,不過是將 data 放置於 ValueStack 中,這個 tag 也是很有用的,而且可以有更短的生命週期、更簡便的 OGNL expression。完整的 tag reference
<s:push value="user">
<s:property value="name"/>
</s:push>

上面的範例顯示了 push tag 在內的生命週期,也就是說,在 push tag 中我們可以不用重新撰寫 user 為起始的 OGNL expression,而我們可以直接取 user object 中的 name property,當然,離開 push tag 之後,我們就不能使用了,所以 push tag 有暫存變數的功能,並且讓我們的 OGNL expression 更簡潔有力!

bean tag
bean tag 看上去就像是 set 與 push tag 的混合版,主要的不同在於我們可以不用再現存的 data 上進行運作,我們可以自己創造一個新的 data,而且可以存放在 ValueStack 或是 ActionContext 中。預設是存放在 ValueStack 中,就如同 push tag 的範例一樣,是在 tag 的開始與結尾之間的 scope 使用!如果我們我要將 data 存放在 ActionContext 中,我們就要指定 id attribute:
<s:bean name="silver8250.bean.HelloBean" id="myBean">
<s:param name="content">Ya!</s:param>
<s:param name="name">Silver</s:param>
<s:property value="sayHello()"/>
</s:bean>
<br />
<s:property value="#myBean.content"/>

上面的範例中,我們建立一個 HelloBean 並且包含 content 與 name properties,還有一個 sayHello() method。透過 bean tag 我們建立出新的 HelloBean 物件命名為 myBean,接著透過 param tag 將 name 與 content properties 賦予 value,並且在 bean tag 生命週期中呼叫 sayHello() method,最後,我們透過 property tag 呼叫儲存在 ActionContext 中的 myBean。

action tag

action tag 讓我們在頁面中呼叫不同 Action。使用 action tag 中最重要的 executeResult attribute,如果我們將 executeResult 設定為 true,則 action tag 就不只是幫我們執行 Action,還會將執行結果列印在畫面上!不過不用擔心,executeResult 預設是 false。一開始可能會覺得 action tag 不是那麼實用,不過當我們有一些 data 放至於某個 Action 中,並且是用來初始畫頁面的 data,那 action tag 就會非常的實用!以下就給個簡單的範例:
<s:action name="Included"></s:action>
<s:property value="#request.message"/>

我們採用 action tag 呼叫 IncludedAction,此 Action 會將一個 message 儲存在 request 中,不過我們現在不將結果印出來,所以採用 executeResult 的預設。使用 action tag 後,IncludedAction 會被執行,然後我們就可以由 request 取得 data。

在這裡我們簡介了 Struts2 framework 中重要的 data tags,當然,還有其他的 tags 我在之後會慢慢的介紹。

2009年4月30日 星期四

[Struts2] 深入探討 OGNL

在之前我們已經知道 OGNL expression 可以幫助我們將 data 在網頁上與後端 Javabean 之間作搬移與轉換型態,而且我們也瞭解到我們的 Actions 會被放在 ValueStack 中讓 OGNL 可以存取。現在我們要告訴你,OGNL 可以存取任何集合的物件,而 ValueStack 只是其中一個集合物件而已!更完整的來說,OGNL 是用來存取 ActionContext 中的 data,而 ActionContext 中包含了 ValueStack。

ActionContext
ActionContext 儲存了 Struts2 framework 中所有的 data,這些 data 包含 ValueStack、Request、Session 與 Application 等。而所有的 OGNL 都會到 ActionContext 中查詢 data,不過如果我們沒有指定 OGNL 要到哪裡存取資料的話,OGNL 預設是到 ValueStack 中進行存取。所謂沒有指定的意思是,如果我們撰寫的 OGNL 為:user.age,這就是採用 OGNL 預設的存取位置。如下圖。
在這裡所謂的 Context 不是指環境,而是一種有 container 概念的物件,主要是蒐集整個 framework 在執行時所需要的 data 與 resources。所有的 OGNL 都會先選擇所要存取的初始地點,如果沒有指定,就是到 ValueStack 中存取。如果我們將之前的 User 物件儲存在 session 中,那我們要存取 session 中 user 的 age,那我們就要改用:#session['user'].age。我們採用 # 開頭並且指定我們要存取的位置為 session,因為 session 是一個 Map 物件(回想我們之前所使用 SessionAware interface 中,session 物件是以 Map 方式被宣告!),我們要採用之前在內建的 OGNL Type Converter 中提到的 Map property 存取方式,所以就會是 #session['user'],取得 user 物件後我們就可以操作內部的 properties!
那我們還有哪些存取位置可以使用呢?下面就列出可用的位置:
  • parameters - 此 request 中的 parameter map
  • request - 如同 JSP 中的 request
  • session - 如同 JSP 中的 session
  • application - 如同 JSP 中的 application
  • attr - 根據 page, request, session 與 application 的順序查詢第一個找到的 data
  • ValueStack - 預設的存取位置
ValueStack
之前我們已經討論過很多關於 ValueStack 的資訊,現在我們要深入的討論 ValueStack。當 Strus2 framework 接收到使用者的 request 後,會先建立一個 ActionContext、ValueStack 與使用者呼叫的 action 物件,並且將 action 中的 properties 儲存到 ValueStack 中。這樣使用者就可以透過 OGNL 存取 ActionContext 中的 data。不過這裡有一點需要注意的是,在 ValueStack 中的 properties 只會出現最上層的 properties,也就是說如果有兩個 properties 是相同的變數名稱,只有最上層的 property 可以被存取。
你可能會好奇為什麼會有這樣情形發生呢?一個 class 中不可能有兩個變數是相同的名稱阿!沒錯!這的確有點弔詭,不過確實是有可能發生的,當我們的 Action 中的 Javabean property 採用 ModelDriven 的方式就會有可能:
public class ModelDrivenAction extends ActionSupport implements ModelDriven<User>
{
private User user = new User();
private String name;
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
@Override
public User getModel()
{
return this.user;
}
@Override
public String execute() throws Exception
{
System.out.println("This.name = "+this.name+"\n User.name="+this.user.getName());
return SUCCESS;
}

}

上面的 ModelDrivenAction 中我們採用 ModelDriven 方式讓 user property 直接存取,然後我們在 execute method 中印出使用者在 textfield 中輸入的值會存在哪個 property 之下,回想一下我們如果使用 ModelDriven 方式,我們的 OGNL 就可以直接寫成:name 而不是 user.name,從 console 中可以發現,ModelDrivenAction 自己定義的 name property 被隱藏了!所以使用者輸入的值只會被除存在 user property 下的 name property!所以在 ValueStack 中會如下:
所以我在將物件作為 Action property 中建議採用 Object-backed Javabean property 方式,這樣可以減少日後維護的問題!

2009年4月21日 星期二

[Struts2] 建立自訂的 OGNL Type Converter

Struts2 framework 中的 OGNL Type Converter 已經滿足了我們大部分的情況,基本上我們可以不用自己建立自訂的 Type Converter,不過 Struts2 framework 還是提供了讓我們可以自訂 Type Converter 的機制!在這裡我將示範要如何建立一個自訂的 OGNL Type Converter!範例中主要是提供一個 UserTypeConverter:將頁面上的 User data 轉換成 Action 中的 User property。

Implement a type converter
Struts2 framework 中,所有的 OGNL Type Converter 都必須 implements TypeConverter interface,這個 interface 提供了 programmers 在任何兩種類型的 data 進行轉換,不過在 Web application 中,我們只需要在 String 與 Object 之間作轉換就可以了!所以,我們自訂的 Type Converter 就採用 Struts2 framework 定義的 StrutsTypeConverter。StrutsTypeConverter 是一個 Abstract class,我們的 Type Converter extends StrutsTypeConverter 之外,還需要實做出兩個 abstract methods:
public abstract Object convertFromString(Map context,
                      String[] values, Class toClass)
public abstract String convertToString(Map context, Object o)
這兩個 methods 從 method name 就可以很清楚的瞭解其目的,convertFromString 主要是將頁面上 String-based data 轉換成 Java type property;而 convertToString 就是將 Java type property 轉換成 String-based data。

Convert between Strings and User
接下來就整個重頭戲了!我們要實際的撰寫我們的 Type Converter:
public class UserTypeConverter extends StrutsTypeConverter
{
  @Override
  public Object convertFromString(Map context, String[] values, Class toClass)
  {
     String name = values[0];
     String password = values[1];
     User user = new User();
     user.setName(name);
     user.setPassword(password);
     return user;
  }
  @Override
  public String convertToString(Map context, Object o)
  {
     User user = (User) o;
     return "User:name="+user.getName()+", password="+user.getPassword();
  }
}

首先,就如我們之前提到的,我們的 Type Converter 要 extends StrutsTypeConverter class,然後實做出兩個 abstract methods。將字串轉換成物件的 method 中,我們主要是將使用者在頁面上輸入兩個字串,而這兩個字串會分別變成 User 物件中的 name 與 password!在這裡沒有什麼邏輯可言,因為我們只是要示範 Type Converter 罷了!反之,將物件轉換成字串時,我們就是將 User 物件顯示成一個字串,這就有點像是 toString() method 一樣,純粹將物件中的資訊轉換成字串!

Configure our type converter
撰寫完我們自訂的 Type Converter 之後,那我們要如何讓 Struts2 framework 知道呢?我們需要一個設定檔!在 Struts2 framework 中提供了兩種方式讓我們可以設定我們的 Type Converter :1) Property-specific 與 2) Global type converter。

1. Property-specific
這種方式的設定檔只能針對某個 Action 的某個 property 給予我們的 Type Converter!首先,我們要先建立一個設定檔,檔案的命名規則為:{ActionName}-conversion.properties。假設我們有一個 Action 為 UserTypeAction,此 Action 中當然要有 User property,然後我們就必須建立一個設定檔名為:UserTypeAction-conversion.properties,而這個 properties 檔案要跟 UserTypeAction 在同一個資料夾中!在設定檔中我們要撰寫如下:
user=silver8250.type_converter.UserTypeConverter

其中,先告知 Struts2 framework 我們要進行轉換的 property 為 user,然後在等號右邊給予我們的 Type Converter!這樣就完成了設定檔案!
這種設定檔到底有什麼好處呢?一開始你應該會很困惑,因為我們不可能為每一個 Action 中有使用 User property 都增加一個設定檔,我們一定是會使用等等會談到的 Global 設定檔!不過在某一種情況下會有用處!假設我們有兩個 Type Converters,第一個是 Global 的,所以我們採用 Global 的設定讓大多數的 property 都使用,不過如我們有某個 Action 的某個 property 要使用我們設計的第二個 Type Converter,這時候 property-specific 就派上用場了!因為 Struts2 framework 會先以 property-specific 設定優先!

2. Global type converter
另一種設定檔就是 Global 型態的 Type Converter,我們只需要一個設定檔為:xwork-conversion.properties 檔案,並且放在 WEB-INF/classes 之下就可以!內容如下:
silver8250.bean.User=silver8250.type_converter.UserTypeConverter

等號左邊是告訴 Struts2 framework 是哪一個 Object 要執行 Type Conversion,右邊則是要使用哪個 Type Converter 進行轉換!這樣就完成了 Global 的設定檔!大多數的情況下我們都是以 Global 設定檔為主!

Use our type converter
最後就是我們在頁面上要如何撰寫呢?其實沒有任何的差別!例如:我們的 Type Converter 是要讓頁面上的兩個字串轉換到 User 中的 name 與 password,所以我們在頁面上就要設計兩個 textfield 讓使用者輸入:
<s:form action="UserType" method="GET">
   <s:textfield name="user.name" label="User Name"></s:textfield>
   <s:textfield name="user.password" label="User Password"></s:textfield>
   <s:submit></s:submit>
</s:form>

你沒看錯!真的沒有差別!真的~

在這裡我們示範了要怎樣設計自己的 Type Converter,不過大多倏地行況下,我們不需要這樣作!

2009年4月15日 星期三

[Struts2] 內建的 OGNL Type Converter

我們已經了解到 OGNL 具備有自動的 data transfer 以及 type converter 的功能,現在我們就要介紹 OGNL 中內建的 type converter。之前我們 Introduce OGNL 中就有簡單的使用過將頁面上 String-based data 轉換成 Javabean 中的 int type data。讓我們來看看這樣的轉換機制到底有哪些其他的功能!

All converters
首先我們先看看 Struts2 framework 中到底有哪些內建的 converters:
  • String - 字串,幾乎是不用進行轉換,因為在 client 端與 server 端的型態就是一樣的!
  • boolean/Boolean - true 或 false 的字串會被轉換
  • char/Character - 可以想像是一個單位的字串
  • int/Integer, float/Float, double/Double, long/Long - 原始的格式被轉換成字串型態,你可以想像就像是使用 String.valueOf() 來進行轉換!
  • Date - 根據使用者目前的 Locale 而被轉換成 SHORT 格式的字串,例如:28/02/97
  • array - 每一個 array 中的 element 將會被轉換成 String 物件做處理
  • List - 預設設定的 Element 以 String 為主
  • Map - 預設設定的 Element 以 String 為主
上述的各種就是 OGNL 內建的 converters,基本上 Primitive type(例如:String, int 等) 是比較好處理也比較可以理解其運作原理;而 Colleciton type(例如:array, List 與 Map) 在轉換上比較複雜,但是對於 programmers 來說還是很直觀的!至於我們要怎樣讓 OGNL 可以使用適當得 type converter,這點我們就無須擔心,只要我們將要取的 data 放置到 ValueStack 中並且在 view-layer 中撰寫適當的 OGNL expression language 就可以了,OGNL 會自動知道使用哪個 type converter!
以下我們將所有的 type converter 分成兩類加以討論:1) Primitive type mapping 與 2) Collection type mapping。

Primitive type mapping
基本型別的轉換是最簡單的,也是很直觀的!我們就直接給個範例:
<s:form action="Register">
<s:textfield name="user.name" label="Name" />
<s:textfield name="user.password" label="Password" />
<s:textfield name="user.age" label="Age" />
<s:submit />
</s:form>

上面所顯示的 form 當使用者按下 submit 之後,就會根據 action attribute 中的 value 將表單傳送給 struts.xml 中設定的 Register action,透過 OGNL 的 expression language 會將各個欄位的值交由 param interceptor 找尋 mapping destination,在由 OGNL 個別進行 type conversion。由上述的 OGNL 中我們可以知道 Register action 中必定會有一個 User object 作為 property:
public class RegisterAction extends ActionSupport
{
private User user;
public void setUser(User user)
{
this.user = user;
}
public User getUser()
{
return this.user;
}
}

我們現在是透過 Object-back Javabean property 的方式(將物件作為 Action property 中提到)存取 User property。而 User object 中一定會有 name, password 與 age 的 property。所以 OGNL 會先透過 RegisterAction 取得 User 物件後,在依照 expression 中不同的 property 去進行 type conversion,例如:age 在 User 物件中屬於 int 型態,則會啟動 int type converter 將使用者輸入的 String type 的 age 轉換成 int type 的 age。
至於為甚麼 HTTP 中的 form 欄位一定都是 String 呢?這點就要牽涉到 HTTP 本身的設計,因為我們傳送的 form 欄位都會以 parameter 的方式傳送,只是有 GET 跟 POST 的差別,這兩種差別在於 GET 會將這些 key value pairs 顯示在 URL 上;而 POST 不會!但是最終都是以 parameter 的方式傳送!也正因為如此,Java EE 中設計的 HttpServletRequest 物件有一個 getParameter() method 就是用來取得使用者在頁面上所傳送的資料。而這個 method 回傳的資料都是 String,所以 OGNL 必須將這些 String-based data 進行 type conversion 了!
同樣的,如果我們要取得某個 property value,我們在頁面上就可以使用 property tag 來取值:
<s:form action="Register">
<s:textfield name="user.name" label="Name" />
<s:textfield name="user.password" label="Password" />
<s:textfield name="user.age" label="Age" />
<s:submit />
</s:form>

這個 OGNL 在後端工作時會轉換成:getUser().getAge();
另外,當 OGNL 在進行 type conversion 時也會進行 validation 的作業!舉個例子來說,如果使用者在 Age 欄位中輸入了非數字的字串,OGNL 在 type conversion 時就會出現錯誤,並且顯示錯誤訊息在使用者所輸入的欄位上,這樣的錯誤機制有點像我們在深入實做 Action 中提到的驗證失敗的訊息。

Collection type mapping
對於上面所提到的 primitive type conversion 的確比較直觀,不過對於 programmer 來說,要讓 String-based 的 HTTP data 能夠 mapping 到 Collection 類的 Java-type 的確比較棘手!好在 OGNL 已經提供了這樣的功能,讓 programmer 不必擔心這類型的轉換工作!
在 Struts2 framework 中提供了將 multivalued request parameters 轉換到有變化性的 Collection 型態 property,而且也包含了原始的 array 型態,畢竟 array 是所有 Collection-type 的基礎型態!以下將分成三種不同型態的 Java-type 分別介紹:1) array, 2) List 與 3) Map。

1. array
Array 其實也算是基本的 Java 型態,只是收集了一堆相同型態的資料罷了!Struts2 framework 提供了這樣的轉換功能,也就是如果我們的 Action 中宣告了 array property(又稱為 indexed Javabeans property),我們可以很輕鬆的完成由網頁上的資料轉換到 Javabean 中的 array。這樣的功能源自於 OGNL 的 navigate(導覽) 能力,因為具有導覽的功能,我們可以期望 OGNL 在 Collection-type 中幫我們找到某個 elements。話不多說,我們就看以下的範例吧:
<s:form action="Regist" method="get">
<s:textfield name="age" label="age"></s:textfield>
<s:textfield name="age" label="age"></s:textfield>
<s:textfield name="age" label="age"></s:textfield>

<s:textfield name="name[0]" label="Name"></s:textfield>
<s:textfield name="name[2]" label="Name"></s:textfield>
<s:textfield name="name[3]" label="Name"></s:textfield>

<s:textfield name="ageInt" label="age int"></s:textfield>
<s:textfield name="ageInt" label="age int"></s:textfield>
<s:textfield name="ageInt" label="age int"></s:textfield>

<s:submit />
</s:form>

首先是頁面的部份,在上面我們宣告了一個 form,不過我們將資料傳送的方式設定為 GET 模式,因為這樣我們就可以觀察 array 型態的資料是怎樣被安排被傳送的。接下來就是重頭戲了!我們有三種 properties,age, name 跟 ageInt。在 age property 中我們測試不要給 index ,看看資料會怎樣被安排;另外就是 name property,這裡的寫法很像是在 Java 中對 array 給值的寫法,我們在這裡故意跳過 index=1 的 array!至於 ageInt 我等等會解釋!
接著就是 Javabean,我們看看 age 跟 name properties 要怎樣在 Javabean 中撰寫:
public class RegistAction extends ActionSupport
{
private Integer[] age;
private String[] name = new String[4];
private int[] ageInt;
public Integer[] getAge()
{
return age;
}
public void setAge(Integer[] age)
{
this.age = age;
}
public String[] getName()
{
return name;
}
public void setName(String[] name)
{
this.name = name;
}
public int[] getAgeInt()
{
return ageInt;
}
public void setAgeInt(int[] ageInt)
{
this.ageInt = ageInt;
}
@Override
public String execute() throws Exception
{
for (int i=0,n=this.age.length;i<n;i++)
System.out.println(this.age[i]);

for (int i=0,n=this.name.length;i<n;i++)
System.out.println(this.name[i]);

for (int i=0,n=this.ageInt.length;i<n;i++)
System.out.println(this.ageInt[i]);

return SUCCESS;
}
}

首先是 age,我們在這裡將他宣告為 Integer 的物件陣列,而 ageInt 則是宣告為 int 的原始型態陣列,等等我們會看到再取值時的差別!再來就是 name property,我們在這裡採用 String 型態,由於 String 不屬於 primitive type 或是 primitive type wrapper 所以我們要自行先宣告陣列大小!這點需要注意一下~否則在 assign value 時 console 會出現錯誤訊息!
在這裡使用陣列跟一般的 property 沒兩樣,我們不必擔心說 assign 具有 index 的 value,因為 OGNL 會幫我們處理,我們只要如同往常提供 getter/setter method 就可以!接下來就是要觀察一下這些 parameters 會怎樣傳送?我們就將 form submit 出去,由 URL 上方可以看到這些 parameters 的結果:
http://localhost:8080/HelloStruts2/Regist.action?age=1&age=2&age=3&name[0]=4&name[2]=5&name[3]=6&ageInt=7&ageInt=8&ageInt=9
由於我們的 form 中有兩種類型的 textfield:一種是有給 index,另一種則是沒有!我們會看到沒有 index 的 value 會被照順序的安排,並且使用相同的 key(如上的 age 與 ageInt),而有給 index 的 value 則會按照我們的 index 作為 key(如上的 name[2])!在 Action 接收後,我們特別在 execute() method 中將這些 array values 印出來觀察,我們發現到 name[1] 確實是沒有值!
最後我們就要看看怎樣將這些 array values 顯示在頁面上:
Age:<s:property value="age"/><br />
Name:<s:property value="name"/><br />
Age Int:<s:property value="ageInt"/><br />

Age[1]:<s:property value="age[1]"/><br />
Name[1]:<s:property value="name[1]"/><br />
Name[2]:<s:property value="name[2]"/><br />
Age Int[1]:<s:property value="ageInt[1]"/><br />

我們分成兩個部份,第一個部份是不給予 index 看看值會怎樣被取出來!第二部份就是給予 index 取出某個 element 的值!第二個部份很好猜測,就和我們先前將 value 寫回 action 中的方式一樣!不過第一部份就比較難猜了!我就直接執行看看結果吧!
Age:1
Name:ognl.NoConversionPossible
Age Int:7, 8, 9
Age[1]:2
Name[1]:
Name[2]:5
Age Int[1]:8
第二部份的結果我就不多說了!只是提醒一下如果是 NULL 的值,回傳到頁面上就會是空白的內容。現在我們就探討一下第一部份的結果。首先,如果我們在 Action 中採用 Primitive type wrapper 物件(例如:Integer, Double 等)宣告陣列,當我們想要印出所有陣列中的元件,我們就要自行 iterate array,如同我們的 age property;如果我們是使用 primitive type 宣告陣列,Struts2 framework 就會自動幫我們 iterate array;另外,如果我們的 array 不屬於 primitive type 或 primitive type wrapper 的話,OGNL 就無法幫我們顯示內容了!

2. List
List 也是在 Struts2 framework 中有支援 navigate 功能,我們可以將上面的範例簡單的修改為 List:
public class RegistAction extends ActionSupport
{
private List<Integer> age;
private List<String> name;
public List<Integer> getAge()
{
return age;
}
public void setAge(List<Integer> age)
{
this.age = age;
}
public List<String> getName()
{
return name;
}
public void setName(List<String> name)
{
this.name = name;
}
}

頁面的部份我們不必修改,因為在 Struts2 framework 中對於 List 與 array 的處理方式其實是類似的,上面的例子中我們使用 Primitive type 作為 List 中的 Element,我們也可以使用非 Primitive type,只要我們使用 J2SE 5.0 的重要特性-Generic 就可以輕鬆完成!如下的 Action 讓我們可以在 List 放我們自己定義的物件:
public class RegistAction extends ActionSupport
{
private List<User> users;
public List<User> getUsers()
{
return users;
}
public void setUsers(List<User> users)
{
this.users = users;
}
}

在頁面上我們就可以採用這樣的 OGNL expression language:users[1].name

3. Map
最後一種 OGNL 可以進行轉換的就是 Map,Map 跟使用 List 上沒有太大的差別,主要差別在於 Map 是以物件作為 key,而 List 是以 index 作為 key。
private Map<String, User> users;
private Map<Integer, User> otherUsers;
public Map<String, User> getUsers()
{
return users;
}
public void setUsers(Map<String, User> users)
{
this.users = users;
}
public Map<Integer, User> getOtherUsers()
{
return otherUsers;
}
public void setOtherUsers(Map<Integer, User> otherUsers)
{
this.otherUsers = otherUsers;
}

上面的 Action 中我們宣告了兩個 Map properties,第一個是用 String 作為 Map 的 Key,第二個則是用 Integer 作為 Map 的 Key。而這兩種 Map 都是存放 User 物件。而我們在頁面存取這兩種 Map 時就要撰寫成如下:
<s:form action="Regist" method="get">
<s:textfield name="users['silver'].name" label="Silver Name"></s:textfield>
<s:textfield name="users.kent.name" label="Kent Name"></s:textfield>

<s:textfield name="otherUsers['0'].name" label="Other User Name"></s:textfield>
<s:submit />
</s:form>

我們存取 Map 的方式可以用 [] 將 Key 寫在裡面,不過要加上 ' ' 符號!或者我們可以直接指定 Key(如:user.kent) 方式,但是當我們的 Key 採用 Integer 時,我們就只能使用第一種表示法存取了!

在這裡我們討論了進階的 OGNL 的 type conversion 機制,從 primitive type 到 collection-based type,如:array, List 與 Map。OGNL 讓 programmers 可以不必煩惱如何將 String-based 的 HTTP value 轉換成 Java-type based 的 Javabean,甚至我們在撰寫 OGNL expression language 時也是很輕鬆容易的!