2009年1月5日 星期一

[Struts] Struts 1.x V.S. Struts 2.x

最近很意外的找到 RoseIndia 這個網站,裡面擁有很多的 tutorials,這篇網誌裡我將會討論在 RoseIndia 中的一篇文章:Struts 1.x Vs Struts 2.x

這篇文章中指出 10 點在 Struts 1.x 中的缺點,在 Struts 2.x 中解決的辦法。

1. Servlet Dependency

在 Struts 1.x 中,programmer 開發一個 Action 時,必須 override execute method,然而 execute method 的 parameter 卻與 HttpServletResponse, HttpServletRequest 物件有相依性,這樣造成 Action 的 execute method 就變得不單純!

反觀 Struts 2.x 中的 Action,我們只要有一個沒有任何 parameter 的 execute method 就可以成為一個 Action,也就是我們只要是一個 POJO(Plain Old Java Object)。這樣的 Action 相對於 Struts 1.x 的 Action 來說,的確是輕量(light-weight)了許多。當然,這樣當 Struts 2.x 的 Action 要使用一些 JSP 中的 Implicit Object (如:request, session 等) 就會比較不方便,其實不方便也倒是還好,為了降低物件之間的 coupling,這樣的不方便就值得了! 順帶一提,Struts 2.x 是採用 IoC 的方式解決這樣的問題。

2. Action classes

承上述中所提到的,Struts 1.x 中的 Action 必須 override execute method,因為 Struts 1.x 中的 Action 都必須 extends Action 這個 abstract class,這樣就造成了 programmer 無法寫出一個很單純的 Action,一旦物件有繼承的關係,這就造成此物件無形中的肥大!

Struts 2.x 中的 Action 是一個 POJO,也就是 programmer 不必去繼承或實作任何的 class 或 interfaces 就可以寫出一個 Action,這樣就讓 Action 物件相對的單純許多! 當然,如果你希望讓 Struts 2 提供更多服務給你所開發的 Action 物件,你可能就必須要實作一些必要的 interfaces,不過比起寄程來說,實作 interface 還是來的單純太多~

3. Validation

Struts 1.x 與 Struts 2.x 都有提供 validate method。不過,Struts 1.x 必須實作在 ActionForm 之上。

而 Struts 2.x 則是在 Action 中 implements Validatable interface 就可以,這當然比 Struts 1.x 的 ActionForm 來得輕量,畢竟繼承所造成的複雜度遠遠超過實作。

4. Threading Model

在 Struts 1.x 中,Action 一定是 thread-safe 或是 synchronized。也就是說 Action 一定是一個 Singleton。

Struts 2.x 則並非如此,每次有一個 request 進來,Struts 2.x 就會為每一個 request 所需求的 Action 初始化一個 instance。

5. Testability

在 Struts 1.x 中測試 Action 是很複雜的,因為 Action 的 execute() method 是帶有 parameter,而這些 parameters 都是 Container 物件~所以測試這樣的 Action 是比較困難的,不過還是有 StrutsTestCase for JUnit 這樣的測試 framework 提供 mock 物件。

由於 Struts 2.x 的 Action 的 execute() method 不帶任何的 parameters,所以在測試 business logic 是很簡單的,使用最原始的 JUnit 就能夠完成測試。

6. Harvesting Input

Struts 1.x 中採用 ActionForm 物件才取得使用者輸入的資料,這樣的 ActionForm 物件必須繼承 ActionForm class,而且不能使用 JavaBean 來代替,也就是 programmer 不可以使用 domain javabean 來取代,所以 programmer 必須重新撰寫一個一模一樣的物件來取得使用者的輸入資料。

在 Struts 2.x 中則不具有這樣的問題,因為 Action 物件本身就會負責取得使用者的輸入資料,而且 Action 物件屬於一個 POJO,使得 Action 物件單純許多,而且,我們還可以重複使用 domain javabean。

7. Expression Language

Struts 1.x 中只有整合 JSP 中提供的 JSTL 與 EL,然而,JSTL 與 EL 只有基本的 object navigator 功能,對於 collection-basec 與 index-based 的物件支援卻很少。

然而,Struts 2.x 中採用了 OGNL 來執行 object navigator,OGNL 對於 collection-based 與 index-based 物件支援很高,並且提供強大的 convertor將 String-based 的 HTTP 資料轉換成 Java type object。

8. Binding values into views

在 Struts 1.x 中採用標準的 JSP 資料儲存機制(也就是 page,request, session, application)。

在 Struts 2.x 中則進一步將這些 JSP 資料儲存機制放置於更高層的 ActionContext,並且提供 ValueStack 來存放 view 中所需要的資料。

9. Type Conversion

Struts 1.x 中的 ActionForm 物件的變數,通常都是以 String 為主,這是因為 HTTP 的資料都是以 String 為主要型態,programmer 必須自行使用 Commons 的 BeanUtils 來進行型態的轉換。

由於 Struts 2.x 中引進了 OGNL 的技術,使得資料型態的轉換皆由 OGNL 來處理,並且支援複雜物件的資料型態轉換。

10. Control of Action Execution

Struts 1.x 中針對每一個 module 的 Action 提供不同的 Request Processor。但是在同一個 module 中的所有 Actions 則是使用同一個 Request Processor。

而 Struts 2.x 中可以針對每一個 Action 提供不同的 interceptor stack,如此可以讓同一個 Action 在不同情況下使用圖一樣的 interceptor stack,進行使得 Action 有不一樣的 life cycle。

[Struts2] How Struts2 works

在這裡,我們將會詳細的描述 Struts2 實際處理的流程。首先,我們先給一張運作圖,如圖一。

image 圖一 Struts2 流程

在這張詳細的流程圖中,我們看到了很多在之前沒有看過的元件:Interceptor、ActionContext、ValueStack 與 OGNL。

由這張圖中可以簡單的瞭解當 FilterDispatcher 根據 action name 所對應到 struts.xml 中的 Action class 之後,Struts2 核心會先呼叫 Interceptor Stack 中的所有 interceptors。接著再呼叫 Action class 中的 execute() method,完成後再呼叫 Result 幫助我們排版,這過程中都會使用到 OGNL 到 ValueStack 中幫助我們取得必要的資料。

接下來我將會描述各個部份的元件。

Interceptor Stack

Interceptor Stack 是在整個流程中第一個被呼叫的,也是最後一個被呼叫的。Interceptor Stack 中包含一集合的 interceptors,而 interceptors 的排列方式是以 stack 的方式排列。Interceptor 提供一種定義各種 workflow 而且以 cross-cutting 的架構,這樣的架構可以使得程式可以很輕易的重複利用,並且可以降低 interceptor 與 Struts2 整體架構的 coupling。

之後我們將會更仔細的描述 interceptor 的全貌,現在先給你一個簡單的概念。

ActionContext, ValueStack 與 OGNL

在 Struts2 中,所有的資料都是儲存在 ActionContext 之中,在這裡,我並不會深入描述 ActionContext,因為現在對你來說還太過於 detail。不過由圖中可以知道,ActionContext 是整個資料 pool 中最上層,而 ValueStack 是屬於其中一個,當然,ActionContext 中還包含了 JSP 的 request, session 等。

ValueStack 在 Struts2 中扮演著資料儲存的主要位置,例如我們所撰寫的 Action 會被置於 ValueStack 中,這也包含了 Action 中的所有變數。而 OGNL 是用來處理存取 ActionContext 中的資料。

關於 ActionContext, ValueStack 與 OGNL 的詳細描述,我也會在之後在詳加說明。

這個部份,我希望你可以先瞭解整個 Struts2 的概觀,至於詳細的細節,之後將會一一的交代。

2009年1月4日 星期日

[Struts2] 初啼試聲 HelloStruts2

上次我們完成了如何在 Eclipse 中開發 Struts2 project。這次我們就小試身手一下~按照各書的慣例,我們也來個 HelloStruts2 project。

1. 我們將之前我們所包裝成 WAR 檔匯入到 Eclipse 中。由 [File] -> [Import] 來進行匯入的動作~接著我們就選擇 Web 底下的 [WAR file],如下圖一。

image
圖一 Import WAR file

2. 在圖一中的 WAR file 選擇上次所包裝的 WAR 檔。並且在 Web Project 中打入您想要的 project 名稱,既然是 HelloStruts2,那我們就輸入 HelloStruts2 囉~

3. 匯入後的 web project,我們可以看到檔案的結構如圖二,src 的部份就讓我們撰寫 source code 之處,而 WebContent 會在我們完成 project 後匯出成 WAR 檔的實際結構。

image
圖二 HelloStruts2 檔案結構

4. 說了這麼多,直接來試試吧~首先我們先在 src 中建立一個新的 class,名稱取為 HelloUser(我會順便輸入 package:silver8250.hellow)。內容就輸入如下:

/**
*
* HelloUser.java class
* at silver8250.hello package
* in HelloStruts2 project
* 2009/1/4 建立
* @author Silver8250
*/
package silver8250.hello;
public class HelloUser
{
private String name;
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public String execute()
{
this.setName("Hello~"+this.getName());
return "success";
}
}

程式中的 execute() method 是身為一個 Action 的必要元素,從 HelloUser class 中可以發現,Struts2 的 Action 不必繼承任何的 super class,只要將提供給外界存取的資料,以符合 JavaBean 的規範來使用就可以!簡單來說,JavaBean 的規範有三點:

1) 一定要有 public default constructo(也就是不帶任何 parameters 的 constructor),如果不撰寫任何的 constructors,compiler 會幫我們完成 default constructor。

2) 欲提供給外界存取的 fields,必須提供適當得 accessor methods 與 mutator methods,也就是由 get/set 為首的 methods(其實沒有那麼簡單)。

3) 一定要是一個可以 Serializable 的 class,也就是至少要 implements java.io.Serializable 這個 interface。

不過對於我們的 Action 來說,只要符合上述的兩點就可以,至於第三點就可有可無囉~其實,這樣的 Action 就是一種 POJO(Plain Old Java Object)

另外,execute() method 要回傳一個 String,這個 String 是要告知 Struts2 在執行完 Action 之後,所要導向的頁面,而 String 的內容會對應到接下來說到的 <result>。

5. 有了 Action,我們就要開始來讓 Struts 2 幫我們讓網頁可以連結到~所以我們要編輯 struts.xml 檔案。在 struts.xml 中我們輸入:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">
<struts>
<constant name="struts.devMode" value="true" />
<package name="hello" extends="struts-default">
<action name="Hello" class="silver8250.hello.HelloUser">
<result name="success">/WEB-INF/pages/hello_user.jsp</result>
</action>
</package>
</struts>

首先,我們會看到 struts.xml 中一定要以 <struts> 作為 root node。接著我們就加入一個 <constant>,這是告知 struts2 目前我們是開發測試階段,這些 constant 是用來複寫 Struts2 中的 intelligent defaults (智慧型預設值,提供事前的預設設定,讓開發者可以無須再設定任何的設定就可以直接開發系統),這些 intelligent defaults 可以在 lib/struts2-core-(version).jar 中的 org.apache.struts2 底下的 default.properties 找到。

在 default.properties 中我們可以找到 struts.devMode 這個設定檔,並請獲得進一步的使用說明。

再來我們就是加入了 <package> 來告知 Struts2 我們在這設定檔中有一些 actions 要可以提供給外界呼叫。package tag 中的 name 是設定 package 的名稱。而 package 的使用可以就像 object 一樣可以有繼承跟抽象化,正如同我們使用 extends 的 attribute 一樣,告知 Struts2 我們的 package 要繼承 struts-default,struts-default 一樣可以在 lib/struts2-core-(version).jar 中找到,初學使用建議還是繼承 struts-default,因為在 struts-default.xml 中設定了很多開發者需要的預設設定,就像是 intelligent defaults 一般。

package 之中我們加入了 <action> 來告知 Struts2 我們有一個 Action 要讓外界存取,name 是用來設定讓外界呼叫的名稱,而 class 則是告訴 Struts2 我們的 Action 是哪個 class,class 的值一定要是完整的名稱(也就是 package 路徑加上 class 名稱)。

在 action 之下,我們又加入了 <result> 的 tag,這個 tag 是呼應到 Action 中的 String 回傳值,就如我們在 Action 的 execute() method 中回傳一個 "success" 的字串,Struts2 會到 action tag 之下的 result 中找尋是否有符合的 result tag 可以對應。所以我們加入的 result tag 中設定 name 為 success,這樣 Action 在執行完後,就會呼叫 <result name="success"> tag 中的設定頁面囉~

6. 針對 Action 與設定完成後,目前來說我們算是完成了 Server 端的設定囉~接下來就是 Client 端的設定,也就是 MVC 中 View 的部份了~

首先,我們在 WebContent 中加入一個新的 JSP 頁面,名為 index.jsp,內容則輸入如下:
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="/struts-tags" prefix="s" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<s:form action="Hello">
<s:textfield name="name" label="Your Name"/>
<s:submit />
</s:form>
</body>
</html>

有接觸過 JSP 的人應該都對於第一個 <%@ page %> 的設定很熟悉了~在此就不多作說明了!而接下來的 <%@ taglib %> 就是設定 tag 的宣告,因為要使用 Struts2 的 tags,所以我們要先宣告,這點對於 Struts2 與 Struts1 有不一樣,Struts2 將所有的 tags 都包在一起了~而 Struts1 則是要分別宣告 HTML 專用的、Logic 專用的或是 bean 專用的等等,有點麻煩~(其實也還好)

中間一大串的 HTML 我就不多作解釋了,我直接說明 <s:form>,用 s 開頭事因為剛剛宣告 tags 的 prefix 宣告,這很像 XML 中的 namespace,可以區分不同領域的相同名稱(如,a:thing 跟 b:thing)。撇開 s: 的 prefix 不談,我想有經驗的 programmer 應該可以瞭解這些 tags 的用意與目的,因為由名稱就可以很直觀的說明了~

<s:form> 就跟 HTML 中的 form 用法一樣,不過我們是採用 action attribute,畢竟後端是呼叫 Action 來執行 business logic 的(我都是這樣記的),而 <s:textfield> 就很明顯是讓使用者可以輸入的一個文字欄位,其中 name 是要設定該欄位中使用者輸入的字串要對應到 Action 的哪個變數,回想剛剛的 Action,我們有宣告一個 String name 的變數,就是要儲存前端的使用者所輸入的字串。而 label attribute 就是產生這個 textfield 的顯示標籤,有點難解釋,等等執行你就會瞭解了~最後一個 <s:submit> 我就不多說了~猜猜吧~

7. 第一個頁面完成後,我們好像還差一個頁面沒有寫耶~有嗎?當然有,回想一下 struts.xml 中的設定,當使用者輸入完成後,Action 執行之後的頁面勒?所以我們根據剛剛在 struts.xml 中的 <result> tag 所寫的在 WEB-INF 中新增一個 pages 的資料夾並加入 success.jsp 頁面。而 success.jsp 頁面我們就填入如下的內容:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib uri="/struts-tags" prefix="s" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<s:property value="name"/>
</body>
</html>

這一頁跟先前的 index.jsp 其實很類似喔~只有倒數第三行的內容不一樣,我們採用 <s:property> tag 來取得 Action 中的值。剛剛我們使用 textfield 將值填入 Action 中的變數,現在我們則使用 property 將值取出來。很直觀吧~

8. 呼~費了好大一番功夫~可不是嗎!我們就要執行看看結果囉~如果你的 HTTP port 為 8080 的話,就輸入:http://localhost:8080/HelloStruts2

看到的頁面應該如圖三。

image
圖三 index.jsp

看到這頁面,在回想一下我們的 index.jsp 中,我們的 textfield 欄位的 label,你應該就可以瞭解 label attribute 的目的了!接著我們就輸入了名字吧~按下 submit 之後就會出現圖四的情形。

image
圖四 success.jsp

還記得我們在 Action 中的 execute() method 是怎樣定義的嗎?我們將使用者輸入的 name 之前加上 "Hello~" 的字串。所以我們在 success.jsp 中取出的結果就會有 Hello~ 的字串在前面囉!

以下就總結一下整個 HelloStruts2 的流程,如下圖五。此流程圖只是簡單的示意圖,實際在運作的過程是很複雜的,往後介紹了 Struts2 的各 components 之後,相信你一定會漸漸的看到全貌!

image

圖五 HelloStruts2 簡單示意圖

1) 使用者在 index.jsp 中輸入 name。

2) Struts2 根據 form 的 action 到 struts.xml 中找到對應的 class,並將 textfield 中的值根據 name 的設定對應到 HelloUser.java 中的 name 變數。

3) Struts2 執行 HelleUser 中的 execute() method 之後,得到 success 的回傳字串,並到 struts.xml 中尋找 action tag 中對應的 result tag。

4) 根據 result tag 中的設定頁面,Struts2 將使用者的 browser 導引到 success.jsp 頁面。

5) success.jsp 頁面中利用 property tag 取得 HelloUser 中的 name 變數,並顯示在頁面上。

這樣簡單的說明了我們整個所作的 HelloStruts2 的流程囉~

簡單得出啼試聲就分享給大家囉~