天天看點

深入淺出JBoss Seam

作者 Michael Yuan譯者 包亮釋出于 2007年11月2日

本文節選了Michael Yuan和Thomas Heute所著的即将出版JBoss Seam: Power and Flexibility Beyond Java EE 5.0第一章和第二章,内容有所删減。

什麼是Seam?

JBoss Seam是“Java EE 5.0的一個輕量級的架構”。這是什麼意思?難道Java EE(Enterprise Edition) 5.0本身不是一套“架構嗎”?為什麼在官方規範之外,還需要另外一個架構?好吧,我們就将seam看作是本應該被包括在Java EE 5.0中的一個“遺漏的架構”吧。它在Java EE 5.0架構的上層,為所有的在企業Web應用中的元件提供了一個統一的、易于了解的程式設計模型。它同樣使基于狀态的應用和業務流程驅動的應用的開發易如反掌。換句話說,Seam緻力于開發者生産力和應用擴充性。

1. 整合和強化Java EE架構

Java EE5.0的核心架構是EJB(Enterprise JavaBeans)3.0和JSF(JavaServer Faces)1.2。EJB 3.0(以下簡稱EJB3)是基于一個POJO(Plain Old Java Objects)的業務服務和資料庫持久化的輕型架構。JSF是一個基于MVC(Model-View-Controller)的Web應用架構。大多數的Web應用都将包含有業務邏輯的EJB3元件和Web應用前端顯示的JSF元件。EJB3和JSF雖然互補,但是他們是根據各自的理念設計的獨立的架構。例如,EJB3使用注解(annotation)來配置服務,而JSF使用的是XML檔案。更進一步講,EJB3和JSF元件在架構層面上是互不敏感的。要整合EJB3和JSF,開發者必須手動地構造facade對象(如:JSF支援bean),将業務元件與Web頁面和樣闆代碼(又稱plumbing代碼)聯結起來,以便能跨架構調用方法。将這些技術粘合起來是Seam的職責之一。

Seam打破了EJB3和JSF之間的人工層,它為整合EJB3和JSF提供了一個一緻的,基于注解的途徑。隻需要個别簡單的注解,Seam中的EJB3業務元件就能直接被用來支援JSF Web表單或者處理Web UI事件。Seam允許開發者将“同一種東西”——有注解的POJOs——應用與所有的應用元件。與其他Web架構開發的應用相比,Seam應用概念簡潔,同樣的功能卻需要較少的代碼(在JAVA和XML中)。如果沒有耐心,或者想要快速預覽,一個Seam到底有多簡單,你可以現看看本文描述的hello world一例。

在JSP來說困難的任務,Seam可以輕易的完成。例如,JSF頭疼的一個問題就是過分依賴HTTP POST。這使得将一個添加到書簽中的JSF網頁,通過HTTP GET通路相當困難。但是有了Seam,生成一個REST網頁是非常容易的。Seam提供了一系列JSF元件标簽和注解,增加了“web友好”和JSF應用的網頁效率。

同時,Seam拓展了EJB3到POJO的元件模式, 從web層到業務層都有了狀态上下文。進一步說,Seam整合了一系列主要的其他開放源代碼架構,例如jBPM、JBoss Rules(又名Drools)、JBoss Portal、JBoss Microcontainer等等。Seam不僅能将它們“有機結合”起來,而且可以像整合JSF和EJB3一樣強化原有的架構。

Seam位于Java EE 5.0底層,但它的應用并不局限與Java EE 5.0伺服器。一個Seam應用可以部署在J2EE 1.4應用伺服器和Tomcat伺服器上。這意味着現在能在Seam應用中得到産品化支援。

1 + 1 > 2

或許有這樣一種誤解,認為Seam僅僅是将各種不同架構串起來的另外一個內建架構。Seam提供了它自身管理的狀态上下文,允許架構通過注解和EL(表達式語言)表達式與其他架構進行深度整合。整合的程式來自于Seam開發者對第三方架構的認知。

2. 一個為ORM設計的Web架構

對象關系映射(ORM)解決方案在當今企業應用中廣為使用。但是,大多數目前的業務和web架構并不是為ORM設計的,它們并不在整個Web互動生命周期——從請求來臨到響應完成——管理持久上下文。這就導緻了包括可怕的LazyInitializationException在内的各種ORM異常,帶來了如“資料傳輸對象(DTO)”等醜陋的伎倆(ugly hacks)。

Gavin King發明了Seam,同時他也發明了在世界上廣為使用的ORM解決方案Hibernate。為了繼承和發揚ORM的最佳實踐,Seam進行了重新設計。有了Seam,就不必再寫DTO,你所做的就是延遲加載。因為擴充後的持久上下文就如同一個自然的高速緩存,可以減少和資料庫的互動,ORM的性能就會被極大地改進。

進一步講,因為Seam整合了ORM層、業務層和表示層,開發者就能夠在表示層直接展示ORM對象,也能把資料庫驗證注解用于輸入表單,以及重新定向ORM例外到定制的錯誤頁面。

3.專為有狀态Web應用而設計

Seam是專為有狀态Web應用而設計的。Web應用是天生的多使用者應用,電子商務應用天生也是有狀态的和有事務的。但是,大多數已有Web應用架構是面向無狀态應用的。開發者必須操作HTTP會話(session)對象來管理使用者狀态,與核心業務邏輯無關的代碼不僅會混亂你的應用,而且帶來了一系列的性能問題。

在Seam中,所有的基礎應用元件天生地有狀态。它們使用起來要比HTTP session容易,因為它們的狀态由Seam公開管理。沒有必要在Seam應用中編寫引起麻煩的狀态管理代碼——隻需在其元件上注解其做用域、生命周期方法以及其他狀态屬性,Seam就會掌管其他[譯者注:指這些元件的生命周期]。Seam狀态元件要比HTTP會話(session)能更好的管理使用者狀态。例如,你能有多個“會話”進行,每個“會話”由在一個HTTP會話(session)中一系列的Web請求和業務方法調用組成。

進一步說,在Seam中,資料庫緩存和事務能自動與應用的狀态相連。Seam在記憶體中自動儲存資料庫更新,等到對話結束後送出到資料庫。記憶體中的緩存能大大減輕複雜狀态應用中資料庫的負載。

除了以上這些,Seam支援整合開源JBoss jBPM業務程式引擎,大大提升了Web應用中的狀态管理。你現在能為一個機構中不同從業人員(諸如客戶、經理、技術支援人員等等)的指定工作流程,利用工作流程來驅動應用,而不是依賴使用者界面事件處理和資料庫。

4. 支援Web 2.0

Seam為Web2.0應用進行了充分的優化。它給AJAX(異步JavaScript和XML,增加網頁互動的一種技術)提供了多種支援——從内置“零Javascript”的AJAX元件到有AJAX支援的JSF元件,再到定制的JavaScript庫,Seam為浏覽器端的Javascript對象提供了直接通路Seam伺服器元件的途徑。Seam提供了一個先進的并發模型,有效的管理來自同一使用者的多個AJAX請求。

對于AJAX應用,不斷增長的資料庫負載是一個巨大的挑戰。與一個非AJAX應用相比,一個AJAX應用要向伺服器發送的更頻繁的請求。一但資料庫必須響應這些AJAX請求,那麼資料庫就不堪重荷。Seam中的狀态持久上下文正如一個記憶體中的緩存,它能在會話始末儲存資訊,最終幫助減少資料庫互動。

Web2.0應用往往為其資料使用複雜關系模型(例如,一個網絡交際站點所做的就是處理和顯示“使用者”之間的關系),對于這些站點,延遲加載對于ORM層至關重要。否則,一個簡單的查詢就能級聯地加載整個資料庫。正如我們前面所讨論過的,Seam是現今唯一一個正确支援Web應用延時加載的Web架構。

5.依賴雙向映射的Pojo服務

Seam是一個“輕量級”架構,因為它使用POJO(plain old Java objects)作為服務元件。在應用中,POJO沒有使用接口或抽象類來"鈎住"元件。當然,問題是如何使POJO互動來組成這個應用?它們如何與容器服務(例如,資料庫持久化服務)互動?

Seam通過使用一個流行的、被稱作依賴注入(DI)的設計模式聯結所有POJO元件。在這個模式下,Seam架構管理着所有元件的生命周期。當一個元件需要使用另外一個時,它通過注解(annotation)向Seam聲明此依賴。Seam依據應用目前狀态得到這個依賴元件,并将它注入到所需求的元件中。

通過拓展依賴注入概念,一個Seam元件A不但可以構造另外一個元件B,而且把此元件B“抛還”給Seam以備其他元件(例如元件C)以後使用。

這類雙向依賴管理甚至都廣泛的應用于簡單的Seam web應用中(例如第二章的hello world一例)。在Seam術語中,我們稱這個為“依賴雙向映射”。

6.非正常的配置

[譯者注:指以隐式映射為主題,以顯式映射為例外的配置方式]

使Seam易用的主要設計原則是“非正常的配置”。其思想是為這些元件提供一系列預設行為,開發者隻需要在預期行為非預設的時候,顯示地配置元件。例如, 當Seam将元件A作為屬性注入到元件B時,預設地,元件A剛會以元件B被注入的屬性的名稱命名。Seam裡還有很類似的細節。總的結果是Seam中配置中繼資料要比其他Java架構簡單的多。是以,大多數的Seam應用能通過一系列簡單的Java注解進行充配置設定置。開發者從減化的複雜度中受益匪淺,最後,與其他Java架構相比,用更少的代碼實作同樣的功能。

7.避免濫用XML

或許你已經注意到,Java注解在表述和處理Seam配置中繼資料時扮演着重要的角色。通過這樣的設計使架構更易于操作。

在J2EE發展早期,XML曾經被看作配置管理的“聖杯”。架構設計者将所有的配置資訊,包括Java類和方法名稱都統統丢進XML文檔,而不考慮對開發者所帶來的後果。檢討後,發現這是個嚴重的錯誤。XML配置文檔太過重複。開發者必須重複代碼中已有的資訊,進而将配置和代碼聯結起來。這些重複使應用易于出錯(例如,一個拼寫錯誤的類名可能在運作時顯示為一個難于調試錯誤)。缺少合理的預設配置進一步使這一問題複雜化。事實上,在一些架構中,相當數量的樣闆代碼僞裝為XML,可能相當于或者超過實際應用中JAVA代碼的數量。對于J2EE開發者,XML的濫用通常被稱為“XML地獄”。

Java社群認識到了XML的濫用問題,并且已經非常成功地用Java代碼中的注解取代了XML。EJB3是Java官方标準化機構促進Java企業元件中注解使用的一項成果。EJB3完全可選擇的使用XML文檔,它向正确方向邁出了積極的一步。Seam加入了EJB3的注解,為整個web應用拓展了基于注解的程式設計模型。

當然,XML對于配置資料并非完全不利。Seam設計者認識到XML适用于指定頁面流程或者定義業務流程的web應用。XML文檔使開發者集中地管理整個web應用的工作流程成為可能,同時也反對将配置資訊分散于java源檔案中。工作流程很少能與源代碼耦合,是以XML文檔中并不需要重複鍵入已存在于代碼中的資訊。

8.為測試而設計

Seam為了易于測試而重新設計。因為所有的Seam元件都是注解過的POJO,它們易于進行單元測試。開發者僅僅通過利用正常的Java new關鍵詞來構造執行個體,然後在測試架構(例如JUnit 或者TestNG)中運作任何方法。如果需要測試多個Seam元件的互動,開發者則逐個執行個體化這些元件,然後手動建立它們的互相關系(也就是顯示地使用setter 方法,而不是依靠Seam依賴注入功能)。

內建測試整個Seam應用比較複雜,因為開發者必須在Seam容器中運作應用。Seam用嵌入的輕量級容器來幫助該類測試。在測試架構中,開發者能按步驟地加載Seam容器,然後運作測試。

9. 卓越的工具支援

對于一個聚焦于開發者生産力的應用架構,開發工具的支援至關重要。Seam釋出了一個基于指令行的生成器,稱作 SeamGen。SeamGen類似于Ruby-On-Rails中的生成器,它支援諸如從一個資料庫生成完整CRUD應用的功能,聰明的開發者會通過諸如“編輯/儲存/在浏覽器重新載入”的步驟、有測試支援的特性,來改進web應用。

但更重要的是,SeamGen生成項目不依賴于主流的Java內建開發環境,如Eclipse和NetBeans。有了SeamGen,開發者可以随時入門。

10. 讓我們開始編碼吧

總而言之,Seam為JavaEE應用削減了開發費用,同時,增加了Java EE 5.0不具有的強大的新功能。在下節(節選自本書第二章),我們将給您展示一些實際代碼例子來闡述Seam如何工作的。你能通過網站http://www.michaelyuan.com/seam/下載下傳到本書中所有的例子的源代碼。

Seam Hello World

JBoss Seam是EJB3和JSF中間的粘合劑,這是Jboss Seam最基本的和最廣泛的應用。通過被Seam管理的元件,Seam允許這兩個架構之間無縫(不是有意雙關的)的內建。它為整個web應用拓展了基于注解的EJB3 POJO程式設計模型。在層與層之間,沒有了必需的手動JNDI查找,沒有了冗長的JSF支援bean的聲明,沒有了過多facade方法,沒有了艱辛的對象傳遞,快哉!

繼續在Seam中使用JavaEE模式

在傳統的java EE應用中,一些設計模式,例如JNDI查找、XML聲明元件、值對象、facade是被強制使用的。Seam用基于注解的POJO消除了這些人為的需求。但是,當Seam應用中真正需要它們的時候,仍然可以自由地使用這些模式。

編寫一個Seam web應用概念上很簡單。你隻需要編碼出下列元件:

  • 實體對象代表資料模型。實體對象可能是JPA或者Hibernate中的POJO對象。它們自動地映射到關系資料庫表。
  • SF web頁面展示了使用者界面。頁面通過表單捕獲使用者的輸入,并且顯示結果。表單域與其資料顯示資料庫表,這些表被映射到實體bean或者實體bean的集合上。
  • EJB3 會話bean或者注解過的Seam POJO可以作為JSF Web頁面的UI事件處理器。它們處理封裝在實體bean中的使用者輸入,為下一步(或者頁面)生成顯示的資料對象。

所有以上元件均由Seam自行管理,它們在運作時被自動注入到正确的頁面或者對象。例如,當使用者單擊按鈕送出一個JSF表單,Seam就會自動解析表單域并構造一個實體bean。然後,Seam将實體bean傳入同樣被Seam構造的事件處理器會話bean中來處理。開發者不需要在代碼中管理元件的生命周期群組件之間的互相關系。依賴處理過程中,沒有樣闆代碼和XML檔案。

本章中,我們使用hello world一例來明确展示Seam如何粘合一個web應用。該例子工作如下:使用者能在web表單中輸入其名字來“問候”Seam。一旦她送出了表單,應用則儲存她的名字到一個關系資料庫中,并且顯示所有已經“問候”過Seam的使用者。該項目示例在該書下載下傳的源代碼中的HelloWorld檔案夾中。為了建立它,你必須安裝Apache ANT 1.6版本以上 (http://ant.apache.org/)。進入HelloWorld目錄,運作指令ant,則會生成build/jars/helloworld.ear檔案,可以直接拷貝該檔案到Jboss AS執行個體的server/default/deploy目錄下。現在,啟動JBoss AS并且打開網址http://localhost:8080/helloworld/。

為了運作本書中的例子,我們建議您使用JEMS GUI安裝程式安裝一個與Seam相容的JBoss AS。您可以從http://labs.jboss.com/portal/jemsinstaller/downloads下載下傳JEMS安裝程式。如果您需要更多安裝JBoss AS和應用部署幫助,請參見附錄A,“安裝和部署JBoss AS”

歡迎使用示例作為模闆,快速開始你自己Seam項目(參見附錄B “使用應用示例作為模闆”)。或者,你能使用指令行工具Seam Gen (參見第四章“快速應用開發工具”)自動生成項目模闆,包括所有的配置檔案。本章中,我将花少量的時間來闡釋源代碼項目中的目錄結構。相反,我們将集中讨論代碼和配置,這也是開發者建立一個Seam 應用必需的。如此,我們就能将知識應用到任何一個項目結構,而不需要受模闆的限制。

源代碼目錄

一個Seam應用由java類和XML或文本配置檔案組成。本書的項目例子中,java源代碼檔案在src目錄中,網頁在view 目錄中,所有的配置檔案都在resources目錄中。更多資訊請看附件B,使用應用示例作為模闆。

1. 建立一個資料模型

Helloworld應用中的資料模型僅僅是一個有name和id屬性的person 類。注解@Entity告訴容器映射該類到一個關系資料庫表,每個屬性對應表中一個字段,每個person執行個體相當于表中的一條記錄。因為Seam采用非正常的配置方式,容器為表名和字段中僅僅使用類名和屬性名。屬性id上的@Id和@GeneratedValue注解暗示id字段是主鍵,它的值是應用伺服器為每個儲存到資料庫的peron對象自動生成。

@Entity
@Name("person")
public class Person implements Serializable {

  private long id;
  private String name;

  @Id @GeneratedValue
  public long getId() { return id;}
  public void setId(long id) { this.id = id; }

  public String getName() { return name; }
  public void setName(String name) {this.name = name;}
}      

Person類中最重要的注解是@Name,它為這個将要注冊于Seam中的Person bean指定了名稱。在其他Seam元件中(例如,頁面和會話bean)中,開發者能指直接使用“person”來引用被管理的Person bean。

2. 将資料模型映射到web表單

在JSF頁面中,我們使用Person bean來支援表單輸入文本域。#{person.name}符号指代名為“person”的Seam元件的name屬性,名為“person”的Seam元件是Person實體bean的一個執行個體。

<h:form>
Please enter your name:<br/>
<h:inputText value="#{person.name}" size="15"/><br/>
<h:commandButton type="submit" value="Say Hello"
                 action="#{manager.sayHello}"/>
</h:form>      

通過以下的實體表單,JSF頁面顯示了資料庫中所有已經向Seam說“hello”的使用者。使用者名單清單存儲在一個名為“fans”的Seam元件中,它是一個List對象。JSFdataTable通過周遊清單,每一行顯示一個Person對象。Fan标記是fans清單的疊代子。

<h:dataTable value="#{fans}" var="fan">
  <h:column>
    <h:outputText value="#{fan.name}"/>
  </h:column>
</h:dataTable>
          
深入淺出JBoss Seam

圖2.1顯示了Hello World網頁

當使用者點選“Say Hello”按鈕送出表單,Seam用輸入資料構造了該person元件。然後它調用了名為“manager”的Seam 元件的sayhello()的方法(像這樣,#{manager.sayHello}是表單送出按鈕的UI事件處理器),這就在資料庫中儲存了person對象并且重新整理了fans清單。名為manager的元件是一個EJB3的會話bean, 我們将在下節讨論該話題。

2. 處理web事件

Seam 中的名為manager的元件是會話bean ManagerAction,正如該類中@Name注解指定的。ManagerAction類有person和fans兩個屬性,這兩個屬性被@In和@Out所注解。

@Stateless
@Name("manager")
public class ManagerAction implements Manager {

  @In @Out
  private Person person;

  @Out
  private List <Person> fans;       

注解@In和@Out在Seam程式設計模型中處于核心。是以,讓我們看看到底它們在這裡是做什麼的。

注解@In告訴Seam,在此會話bean中,執行任何一個方法之前,Seam就會把由JSF表單構造的名為person元件賦給該person字段(通過依賴注入)。開發者能為@In中的注入的元件指定一個任意的名稱,但是如果沒有指定,如這裡所示,Seam會将同類型以及同名稱的元件注入到該字段中。注解@Out告訴Seam,在執行任何方法後,Seam會将屬性fans值和屬性person的值都賦給被Seam管理的同名的元件。在Seam中,我們将這個操作稱作 “依賴抛出”。以此,在ManagerAction.sayHello()方法中,我們僅僅需要更新屬性fans和屬性person的值,它們會自動顯示在頁面上。

什麼是雙向映射

在Seam 檔案中,有時你就會看到術語“雙向映射”。它指的是被Seam管理的元件和Seam管理上下之間的注入和抛出。

因為person屬性已經通過注入持有了表單資料,sayHello()方法僅僅是通過JPAEntityManager将它儲存到資料庫中,JPAEntityManager也是通過@PersistenceContext注入的。當方法傳回之後,它便更新了fans和person對象并且把這兩個對象抛出。方法sayHello()一般會傳回null,預示着在調用之後,更新的資料模型将在目前的JSF頁面顯示。

@PersistenceContext
  private EntityManager em;

  public String sayHello () {
    em.persist (person);
    person = new Person ();
    fans = em.createQuery("select p from Person p")
          .getResultList();

    return null;
  }      

除了一些細節,我們基本完成了。可能你已經注意到,ManagerAction bean類實作了Manager接口。為了符合EJB3會話bean 規範,我需要一個能列出bean中所有業務方法的方法。下面是接口Manager代碼,幸運的是,用任何進階IDE工具都能輕松地自動生成這個接口。

@Local
public interface Manager {
  public String sayHello ();
}      

這就是在Hello World例子中需要的所有代碼。後面兩章小節将涵蓋Seam應用的其他方法和配置。如果開發者為了自己的小型資料庫應用想立即編碼和定制helloworld項目,那麼現在就可以跳過本章的剩餘部分。

4. 更易于了解的seam程式設計模型

現在我們已經大緻了解了Hello World的應用。但是我們還有一些重要的話題繼續,例如其他折中途徑以及前面代碼沒有涉及到重要特性,我們将在本節讨論這些話題。它們能幫助開發者對seam更深刻的了解,但是如果你沒有耐心,可以直接跳過本節,需要的時再來閱讀。

4.1 Seam POJO元件

上例中,我們用一個EJB3會話bean實作了應用邏輯,但是我們并不局限于EJB3元件。事實上,Seam中任何一個有@Name注解的POJO都能被轉化為一個可管理的元件。

例如,我們能将ManagerAction轉化為一個 POJO,而不是一個EJB3 session bean。

@Name("manager")
public class ManagerAction {

  @In (create=true)
  private EntityManager em;

  ... ...
}       

使用POJO取代EJB3 bean有正反兩方面意見,使用POJO程式設計時很簡單,因為它們不需要EJB3特有的注解和接口(參見上文)。如果你的所有業務元件都是Seam POJO, 那麼你就能不依賴EJB3應用伺服器,運作你的Seam 應用(參見23章,沒有EJB3的Seam)。

但是,POJO比EJB3的功能少,因為POJO不能獲得EJB3容器服務。在不依賴EJB3的Seam 中喪失的EJB3服務就包括以下幾點:

  • @PersistenceContext注入在POJO中不在管用。為了在一個Seam POJO中得到EntityManager,開發者不得不在Seam配種檔案中初始化EntityManager,然後使用Seam注解@In将它注入到POJO中。
  • POJOs中将不在支援方法級别事務聲明(declarative method-level transaction)。相反,你可以配置Seam來劃分事務,可以從收到web請求開始直到響應頁面産生結束。
  • Seam POJO不是消息驅動元件。
  • 不支援注解為@Asynchronous的方法。
  • 不支援容器安全管理。
  • 沒有事務或者元件級别的持久上下文。Seam POJO中的所有的持久上下文都是經過拓展的(更多細節請參見7.1 “預設的對話作用域”)。
  • 沒有內建容器管理的體系結構(例如,JMX控制台服務)。
  • Seam POJO方法中沒有Java RMI。
  • Seam POJO不能是注解為@WebService元件。
  • 沒有JCA內建。

是以當在EJB3容器中進行部署時,為什麼每個人都想使用POJO元件?答案就是,POJO元件對于純“業務邏輯”元件非常有益。POJO為其他元件代理了資料通路、消息傳遞和其他基本功能。例如,我們能使用POJO元件操縱Seam資料通路對象,這對“業務邏輯”POJO是非常有用的,因為它們可以在需要的時候,在其他架構中被重用。但是總的來說,它們的應用要比EJB3元件少,特别是在中小型應用中。是以,本書的大多數例子我們都使用EJB3元件。

4.2 易于測試

我們已經在第一章中提到,Seam為了不依賴容器的友善的測試,進行了重新設計。在helloworld項目中,我們在測試檔案夾中包括了單元測試和內建測試這兩個測試用例。在純Java SE環境下,Seam 測試體系模拟了資料庫、JSF、Seam上下文以及其他應用伺服器服務,隻要運作ant test指令就能運作所有的測試。

4.3 基于Getter和Setter的雙向映射

在Hello World一例中,我們已經展示了通過成員變量對Seam元件進行的雙向映射,你也能通過Getter和Setter方法對元件進行雙向映射。例如,以下代碼就工作的很好。

private Person person;
private List <Person> fans;

@In
public void setPerson (Person person) {
  this.person = person;
}
@Out
public Person getPerson () {
  return person;
}
@Out
public List <Person> getFans () {
  return fans;
}      

雖然以上的getter和setter方法看似輕微,利用getter和setter方法的雙向映射真正的價值在于能其加入定制邏輯來操縱雙向映射的過程。例如,你可以驗證被注入的對象或者快速地從資料庫重新得到被抛出的對象。

4.4避免過度的雙向映射

在Hello World一例中 ,通過将資料元件作為業務元件的屬性,可以輕易的減少或者消除雙向映射。在JSF頁面中,通過這種方式,開發者隻需要引用業務元件,而不需要在業務元件和資料元件之間的雙向映射。例如,開發者可以修改 ManagerAction類為以下所述。

依賴雙向映射是一個非常實用的設計模式。但是,正如其他設計模式,過度使用就會有害。過度的依賴雙向映射讓代碼變得難以閱讀,因為開發者必須了解每個注入的元件出自何處。過度的依賴雙向映射也能增加性能消耗,因為雙向映射是在運作時進行。

@Stateless
@Name("manager")
public class ManagerAction implements Manager {

  private Person person;
  public Person getPerson () {return person;}
  public void setPerson (Person person) {
    this.person = person;
  }

  private List <Person> fans;
  public List<Person> getFans () {return fans;}

...  ...
}      

接下來,我們在頁面上引用的屬性如下:

<h:form>

Please enter your name:<br/>

<h:inputText value="#{manager.person.name}"/>
<br/>
<h:commandButton type="submit" value="Say Hello"
                 action="#{manager.sayHello}"/>
</h:form>      
... ...      
<h:dataTable value="#{manager.fans}" var="fan">
  <h:column>
    <h:outputText value="#{fan.name}"/>
  </h:column>
</h:dataTable>      

最後,具有了依賴管理的Seam是多用的。通常用資料通路業務元件封裝資料是一項好的實踐,特别是針對有狀态業務元件。

4.5 JSF中的頁面導航

本例中,隻有一個頁面。每次點選按鈕後,JSF頁面會重新顯示更新過的資料模型。顯然,大多數web應用多于一個頁面。在JSF中,一個使用者界面事件處理器能通過傳回導航規則名稱,決定下一步該顯示哪個頁面。例如,開發者可以在navigation.xml中定義以下導航規則。

<navigation-case>
  <from-outcome>anotherPage</from-outcome>
  <to-view-id>/anotherPage.jsp</to-view-id>
</navigation-case>
      

之後,如果sayHello()方法傳回一個名為“another page”的字元串,JSF下一步就該展示anotherPage.jsp。UI事件處理器決定了接下來要顯示哪個頁面,進而為我們帶來了有步驟的控制。

4.6 通過EntityManager通路資料庫

JPA(Java Persistence API)EntityManager管理着關系資料庫表與實體bean 之間的映射。EntityManager 在運作時由應用伺服器建立。你能使用注解@PersistenceContext,注入一個EntityManager的執行個體。

EntityManager.persist()方法将實體bean存為與之對應資料表的一條記錄。EntityManager.query()方法運作SQL化的查詢,并以實體bean集合形式從資料庫傳回資料。更多細節請參考JPA檔案中關于如何使用EntityManager和查詢語言。在本書中,我們隻用最簡單的查詢語句。

預設地,EntityManager将資料存于嵌入的HSQL資料庫中。如果在本機上運作Jboss AS,可以通過以下步驟,為HSQL資料庫開啟一個GUI控制台:通路http://localhost:8080/jmx-console/,點選database=localDB,service=HypersonicMBean服務,之後,點選在startDatabaseManager方法下方的“invoke”按鈕。你就可以從控制台執行任意SQL指令。

5. 配置和打包

下面,我們将轉移話題,讨論配置檔案和應用程式打包。實際上,你可以通過Seam Gen指令行工具,生成幾乎所有的配置文檔和構造腳本檔案。或者你也可以簡單的重用在示例中的源檔案。是以,如果你想首先學習Seam程式設計技術,但又擔心接下來的配置和部署,這個是很正常的。你可以完全放心地跳過本節,需要的時候可以再次閱讀。

本節中,我們集中探讨Seam EJB3元件配置,JBoss AS外的Seam POJO配置和部署當然是可行的。

大多數Seam配置檔案都是XML文檔。但是等等!我們剛才不是承諾Seam能讓我們擺脫J2EE和Spring中的XML地獄嗎?為什麼它又有了XML文檔呢? 是的,XML文檔确實有很多用處。XML文檔非常适合部署階段的配置(例如web應用的根URL和背景資料庫的定位)。因為它允許我們在部署階段改變配置而不需要改變和重新編譯源代碼。它也适合粘合應用伺服器中的不同子系統(例如,配置如何讓JSF元件與Seam EJB3元件互動)。XML文檔也非常适合表示層相關内容(例如網頁和頁面導航流程)。

我們反對在XML文檔中重複已經存在于Java源代碼中的資訊。開發者很快就會發現,這個簡單的SeamEJB3 應用有多個XML配置文檔,每個文檔那個都非常簡短,并且沒有一個包含存在于Java代碼中的資訊。換句話說,Seam中沒有“XML代碼”。

進一步講,XML文檔中的大多數内容都是靜态的。是以開發者能在自己的Seam應用中輕松地重用這些文檔。如何使用示例作為自己的應用模闆的介紹,請參見附錄B——使用應用示例作為模闆。

我們将用下面幾頁來詳細講解示例應用的配置文檔和打包後的目錄結構。如果你沒有耐心看下去,而且很滿意這個應用模闆,你可以跳過以下内容。不管怎樣,不再羅嗦, 我們一起來了解hello world示例是如何進行配置和打包的。為了建構一個JBoss AS的部署Seam 應用,我們必須将以上所有java 類和配置文檔打包為企業應用程式歸檔(EAR)。該例中,EAR檔案是helloworld.ear。它包含了三個JAR檔案那個和兩個XML配置文檔。

helloworld.ear
|+ app.war        //包含Web頁面等
|+ app.jar        //包含Seam元件
|+ jboss-seam.jar // Seam庫
|+ META-INF
   |+ application.xml
   |+ jboss-app.xml        

源代碼目錄

在此項目的源代碼中,resources/WEB-INF目錄包含屬于app.war/WEB-INF目錄的配置文檔。resources/META-INF目錄包含屬于app.jar/META-INF和helloworld.ear/META-INF的文檔。Resources根目錄包含屬于根目錄app.jar的文檔。

application.xml文檔列出了在EAR中的JAR檔案,并為該應用指定了根URL。

<application>
  <display-name>Seam Hello World</display-name>

  <module>
    <web>
      <web-uri>app.war</web-uri>
      <context-root>/helloworld</context-root>
    </web>
  </module>

  <module>
    <ejb>app.jar</ejb>
  </module>

  <module>
    <java>jboss-seam.jar</java>
  </module>

</application>      

jboss-app.xml文檔為該應用指定了類加載器,每個EAR應用的類加載器應該有一個唯一的名稱。這裡我們使用應用程式名作為類加載器的名稱,以避免重複。

<jboss-app>
  <loader-repository>
    helloworld:archive=helloworld.ear
  </loader-repository>
</jboss-app>
      

jboss-seam.jar是Seam釋出Seam類庫。app.war和app.jar文檔由我們來建構。是以,下面我們要研究app.war和app.jar。

5.1. WAR檔案

app.war是按照Web應用程式歸檔規範打包的JAR檔案,它包含頁面和标準的JSF/Seam配置文檔。你還可以将JSF特有的類庫檔案放入WEB-INF/lib目錄 (例如jboss-seam-ui.jar)。

app.war
|+ hello.jsp
|+ index.html
|+ WEB-INF
   |+ web.xml
   |+ faces-config.xml
   |+ components.xml
   |+ navigation.xml      

web.xml文檔是所有java EE web應用必需的。JSF用它來配置JSF servlet控制器,Seam用它來攔截所有的web請求。該配置文檔的相當标準。

<web-app version="2.4"
    xmlns="http://java.sun.com/xml/ns/j2ee"
    xmlns:xsi="..."
    xsi:schemaLocation="...">

  <!-- Seam -->
  <listener>
    <listener-class>
      org.jboss.seam.servlet.SeamListener
    </listener-class>
  </listener>

  <!-- MyFaces -->
  <listener>
    <listener-class>
org.apache.myfaces.webapp.StartupServletContextListener
    </listener-class>
  </listener>

  <context-param>
    <param-name>
      javax.faces.STATE_SAVING_METHOD
    </param-name>
    <param-value>client</param-value>
  </context-param>

  <servlet>
    <servlet-name>Faces Servlet</servlet-name>
    <servlet-class>
      javax.faces.webapp.FacesServlet
    </servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>

  <!-- Faces Servlet Mapping -->
  <servlet-mapping>
    <servlet-name>Faces Servlet</servlet-name>
    <url-pattern>*.seam</url-pattern>
  </servlet-mapping>
  <context-param>
    <param-name>javax.faces.CONFIG_FILES</param-name>
    <param-value>/WEB-INF/navigation.xml</param-value>
  </context-param>
</web-app>      

faces-config.xml文檔是JSF标準的配置文檔,Seam用它來将其攔截器添加到JSF生命周期中。

<faces-config>

  <lifecycle>
    <phase-listener>
      org.jboss.seam.jsf.SeamPhaseListener
    </phase-listener>
  </lifecycle>

</faces-config>      

navigation.xml文檔為多頁面應用包含JSF頁面導航規則。因為hello world示例隻有一個簡單的頁面,是以該文檔是空的。

components.xml文檔包含Seam特有的配置選項,除jndi-pattern屬性以外,其他都不依賴于應用。該屬性必須包括EAR文檔的名稱,以便Seam通過其的JNDI全名通路EJB3 bean。

<components ...>

  <core:init
    jndi-pattern="helloworld/#{ejbName}/local"
    debug="false"/>

  <core:manager conversation-timeout="120000"/>

</components>      

5.2. Seam元件JAR包

app.jar文檔包含所有的EJB3bean 類(實體bean和會話bean)以及EJB3相關的配置文檔。

app.jar
|+ Person.class        // entity bean
|+ Manager.class       // session bean interface
|+ ManagerAction.class // session bean
|+ seam.properties     // empty file but needed
|+ META-INF
   |+ ejb-jar.xml
   |+ persistence.xml      

seam.properties文檔這兒是空但必需的,因為Jboss要通過它知道此JAR檔案包含Seam EJB3 bean類,并且相應地處理注解。

ejb-jar.xml文檔包含額外的配置資訊,這些資訊能重載或者增補EJB3 bean上的注解。在一個Seam應用中,它能将所有的EJB3 類加入Seam攔截器。我們能在所有的Seam應用中重用該文檔。

<ejb-jar>
  <assembly-descriptor>
    <interceptor-binding>
      <ejb-name>*</ejb-name>
      <interceptor-class>
        org.jboss.seam.ejb.SeamInterceptor
      </interceptor-class>
    </interceptor-binding>
  </assembly-descriptor>
</ejb-jar>      

persistence.xml文檔為EJB3 實體bean配置了背景資料源。本例中,我們隻是使用了被嵌入到JBoss AS中預設的HSQL資料庫(也就是java:/DefaultDS資料源)。

<persistence>
  <persistence-unit name="helloworld">
    <provider>
      org.hibernate.ejb.HibernatePersistence
    </provider>
    <jta-data-source>java:/DefaultDS</jta-data-source>
    <properties>
      <property name="hibernate.dialect"
          value="org.hibernate.dialect.HSQLDialect"/>
      <property name="hibernate.hbm2ddl.auto"
          value="create-drop"/>
      <property name="hibernate.show_sql" value="true"/>
    </properties>
  </persistence-unit>
</persistence>
      

這樣,以上就是一個簡單Seam應用所需的所有配置和打包。我們将在以後讨論到本書的更進階的主題時,涵蓋更多的配置選項和類庫。再次強調一下,Seam應用入門最簡單的方法就是,不要擔心這些配置檔案,隻需要從已有的應用模闆做起。

6. 為何這麼簡單?

這就是hello world 應用,三個簡單的Java類,一個JSF頁面,一組靜态配置檔案。我們已經有了一個完整的資料庫驅動的web應用。整個應用隻需要的是少于30行的Java代碼,沒有一處“XML代碼”。但是如果開發者有PHP背景,你可能仍然會問“何以這麼簡單?我能在php中使用更少的代碼嗎?”

好吧,答案就是Seam應用在理論上要比PHP(或者其他任何一種腳本語言)應用簡單的多。Seam 元件模式允許我們,有所控制地,可維護地給應用增加更多的功能。我們很快就會發現,Seam元件使開發有狀态的和有事務的web應用變得易如反掌。對象關系映射架構(例如:實體bean)允許我們将注意力放在抽象資料模型上,而不需要處理資料庫特有的SQL語句。

本文是基于該書的第一章和第二章。後面的章節中,我們繼續讨論如何使用Seam元件繼續開發複雜的Seam 應用。參見本書目錄來檢視本書的所有主題。

請看Gavin King的兩個訪談錄來檢視以往關于Seam的話題。

  • JBoss Seam 1.1 Indepth: An Interview with Gavin King
  • JBoss Seam 1.0: rethinking web application architecture

關于作者

Dr. Michael Yuan是JBoss Seam: Simplicity and Power Beyond Java EE 5.0一書的作者。該書探讨了下一代web應用架構。他同時也是Nokia Smartphone Hacks一書和其他三本技術讀物的作者。Michael潛心研究輕量級企業web應用,終端對終端的移動應用開發。你可以通過他的部落格聯系他。

檢視英文原文:Introduction to JBoss Seam