国产一级a片免费看高清,亚洲熟女中文字幕在线视频,黄三级高清在线播放,免费黄色视频在线看

打開APP
userphoto
未登錄

開通VIP,暢享免費(fèi)電子書等14項(xiàng)超值服

開通VIP
JBoss Seam 入門

Chapter 1. Seam 入門

1.1. 試試看

本教程假定你已下載JBoss AS 4.0.5并安裝了EJB 3.0 profile(請使用JBoss AS安裝器)。你也得下載一份Seam并解壓到工作目錄上。

各示例的目錄結(jié)構(gòu)仿效以下形式:

  • 網(wǎng)頁、圖片及樣式表可在 examples/registration/view 目錄中找到。

  • 諸如部署描述文件及數(shù)據(jù)導(dǎo)入腳本之類的資源可在目錄 examples/registration/resources 中找到。

  • Java源代碼保存在 examples/registration/src 中。

  • Ant構(gòu)建腳本放在 examples/registration/build.xml 文件中。

1.1.1. 在JBoss AS上運(yùn)行示例

第一步,確保已安裝Ant,并正確設(shè)定了 $ANT_HOME 及 $JAVA_HOME 的環(huán)境變量。接著在Seam的根目錄下的 build.properties 文件中正確設(shè)定JBoss AS 4.0.5的安裝路徑。 若一切就緒,就可在JBoss的安裝根目錄下敲入 bin/run.sh 或 bin/run.bat 命令來啟動JBoss AS。(譯注:此外,請安裝JDK1.5以上以便能直接運(yùn)行示例代碼)

現(xiàn)在只要在Seam安裝目錄 examples/registration 下輸入 ant deploy 就可構(gòu)建和部署示例了。

試著在瀏覽器中訪問此鏈接:http://localhost:8080/seam-registration/

1.1.2. 在Tomcat服務(wù)器上運(yùn)行示例

首先,確保已安裝Ant,并正確設(shè)定了 $ANT_HOME 及 $JAVA_HOME 的環(huán)境變量。接著在Seam的根目錄下的 build.properties 文件中正確設(shè)定Tomcat 6.0的安裝路徑。你需要按照25.5.1章節(jié)“安裝嵌入式的Jboss”中的指導(dǎo)配置 (當(dāng)然, SEAM也可以脫離Jboss在TOMCAT上直接運(yùn)行)。

至此,就可在Seam安裝目錄 examples/registration 中輸入 ant deploy.tomcat 構(gòu)建和部署示例了。

最后啟動Tomcat。

試著在瀏覽器中訪問此鏈接:http://localhost:8080/jboss-seam-registration/。

當(dāng)你部署示例到Tomcat時(shí),任何的EJB3組件將在JBoss的可嵌入式的容器,也就是完全獨(dú)立的EJB3容器環(huán)境中運(yùn)行。

1.1.3. 運(yùn)行測試

幾乎所有的示例都有相應(yīng)的TestNG的集成測試代碼。最簡便的運(yùn)行測試代碼的方法是在 examples/registration目錄中運(yùn)行 ant testexample。當(dāng)然也可在IDE開發(fā)工具中使用TestNG插件來運(yùn)行測試。

1.2. 第一個例子:注冊示例

注冊示例是個極其普通的應(yīng)用,它可讓新用戶在數(shù)據(jù)庫中保存自己的用戶名,真實(shí)的姓名及密碼。 此示例并不想一下子就把Seam的所有的酷功能全部秀出。然而, 它演示了EJB3 會話Bean作為JSF動作監(jiān)聽器及Seam的基本配置的使用方法。

或許你對EJB 3.0還不太熟悉,因此我們會對示例的慢慢深入說明。

此示例的首頁顯示了一個非常簡單的表單,它有三個輸入字段。試著在表單上填寫內(nèi)容并提交,一旦輸入數(shù)據(jù)被提交后就會在數(shù)據(jù)庫中保存一個user對象。

1.2.1. 了解代碼

本示例由兩個JSP頁面,一個實(shí)體Bean及無狀態(tài)的會話Bean來實(shí)現(xiàn)。

讓我們看一下代碼,就從最“底層”的實(shí)體Bean開始吧。

1.2.1.1. 實(shí)體Bean:User.java

我們需要EJB 實(shí)體Bean來保存用戶數(shù)據(jù)。這個類通過注解聲明性地定義了 persistence 及 validation 屬性。它也需要一些額外的注解來將這個類定義為Seam的組件。

Example 1.1. 

@Entity                                                                                  (1)@Name("user")                                                                            (2)@Scope(SESSION)                                                                          (3)@Table(name="users")                                                                     (4)public class User implements Serializable{   private static final long serialVersionUID = 1881413500711441951L;   private String username;                                                              (5)   private String password;   private String name;   public User(String name, String password, String username)   {      this.name = name;      this.password = password;      this.username = username;   }   public User() {}                                                                      (6)   @NotNull @Length(min=5, max=15)                                                       (7)   public String getPassword()   {      return password;   }   public void setPassword(String password)   {      this.password = password;   }   @NotNull   public String getName()   {      return name;   }   public void setName(String name)   {      this.name = name;   }   @Id @NotNull @Length(min=5, max=15)                                                   (8)   public String getUsername()   {      return username;   }   public void setUsername(String username)   {      this.username = username;   }}
(1)

EJB3標(biāo)準(zhǔn)注解 @Entity 表明了 User 類是個實(shí)體Bean.

(2)

Seam組件需要一個 組件名稱,此名稱由注解 @Name來指定。此名稱必須在Seam應(yīng)用內(nèi)唯一。當(dāng)JSF用一個與組件同名的名稱去請求Seam來解析上下文變量, 且該上下文變量尚未定義(null)時(shí),Seam就將實(shí)例化那個組件,并將新實(shí)例綁定給上下文變量。 在此例中,Seam將在JSF第一次遇到名為 user 的變量時(shí)實(shí)例化 User

(3)

每當(dāng)Seam實(shí)例化一個組件時(shí),它就將始化后的實(shí)例綁定給組件中 默認(rèn)上下文 的上下文變量。默認(rèn)的上下文由 @Scope注解指定。 User Bean是個會話作用域的組件。

(4)

EJB標(biāo)準(zhǔn)注解@Table 表明了將 User 類映射到 users 表上。

(5)

name、 password 及 username 都是實(shí)體Bean的持久化屬性。所有的持久化屬性都定義了訪問方法。當(dāng)JSF渲染輸出及更新模型值階段時(shí)需要調(diào)用該組件的這些方法。

(6)

EJB和Seam都要求有空的構(gòu)造器。

(7)

@NotNull 和 @Length 注解是Hibernate Validator框架的組成部份, Seam集成了Hibernate Validator并讓你用它來作為數(shù)據(jù)校驗(yàn)(盡管你可能并不使用Hibernate作為持久化層)。

(8)

標(biāo)準(zhǔn)EJB注解 @Id 表明了實(shí)體Bean的主鍵屬性。

這個例子中最值得注意的是 @Name 和 @Scope 注解,它們確立了這個類是Seam的組件。

接下來我們將看到 User 類字段在更新模型值階段時(shí)直接被綁定給JSF組件并由JSF操作, 在此并不需要冗余的膠水代碼來在JSP頁面與實(shí)體Bean域模型間來回拷貝數(shù)據(jù)。

然而,實(shí)體Bean不應(yīng)該進(jìn)行事務(wù)管理或數(shù)據(jù)庫訪問。故此,我們無法將此組件作為JSF動作監(jiān)聽器,因而需要會話Bean。

1.2.1.2. 無狀態(tài)會話Bean:RegisterAction.java

在Seam應(yīng)用中大都采用會話Bean來作為JSF動作監(jiān)聽器(當(dāng)然我們也可選擇JavaBean)。

在我們的應(yīng)用程序中確實(shí)存在一個JSF動作和一個會話Bean方法。在此示例中,只有一個JSF動作,并且我們使用會話Bean方法與之相關(guān)聯(lián)并使用無狀態(tài)Bean,這是由于所有與動作相關(guān)的狀態(tài)都保存在 User Bean中。

這是示例中比較有趣的代碼部份:

Example 1.2. 

@Stateless                                                                               (1)@Name("register")public class RegisterAction implements Register{   @In                                                                                   (2)   private User user;   @PersistenceContext                                                                   (3)   private EntityManager em;   @Logger                                                                               (4)   private Log log;   public String register()                                                              (5)   {      List existing = em.createQuery(         "select username from User where username=#{user.username}")                    (6)         .getResultList();      if (existing.size()==0)      {         em.persist(user);         log.info("Registered new user #{user.username}");                               (7)         return "/registered.jsp";                                                       (8)      }      else      {         FacesMessages.instance().add("User #{user.username} already exists");           (9)         return null;      }   }}
(1)

EJB標(biāo)準(zhǔn)注解 @Stateless 將這個類標(biāo)記為無狀態(tài)的會話Bean。

(2)

注解 @In將Bean的一個屬性標(biāo)記為由Seam來注入。 在此例中,此屬性由名為 user 的上下文變量注入(實(shí)例的變量名)。

(3)

EJB標(biāo)準(zhǔn)注解 @PersistenceContext 用來注入EJB實(shí)體管理器。

(4)

Seam的 @Logger 注解用來注入組件的 Log 實(shí)例。

(5)

動作監(jiān)聽器方法使用標(biāo)準(zhǔn)的EJB3 EntityManager API來與數(shù)據(jù)庫交互,并返回JSF的輸出結(jié)果。 請注意,由于這是個會話Bean,因此當(dāng) register() 方法被調(diào)用時(shí)事務(wù)就會自動開始,并在結(jié)束時(shí)提交(commit)。

(6)

請注意Seam讓你在EJB-QL中使用JSF EL表達(dá)式。因此可在標(biāo)準(zhǔn)JPA Query 對象上調(diào)用普通的JPA setParameter() 方法,這樣豈不妙哉?

(7)

Log API為顯示模板化的日志消息提供了便利。

(8)

多個JSF動作監(jiān)聽器方法返回一個字符串值的輸出,它決定了接下來應(yīng)顯示的頁面內(nèi)容。 空輸出(或返回值為空的動作監(jiān)聽器方法)重新顯示上一頁的內(nèi)容。 在普通的JSF中,用JSF的導(dǎo)航規(guī)則(navigation rule) 來決定輸出結(jié)果的JSF視圖id是很常用的。 這種間接性對于復(fù)雜的應(yīng)用是非常有用的,值得去實(shí)踐。但是,對于象示例這樣簡單的的應(yīng)用,Seam讓你使用JSF視圖id作為輸出結(jié)果,以減少對導(dǎo)航規(guī)則的需求。請注意,當(dāng)你用視圖id作為輸出結(jié)果時(shí),Seam總會執(zhí)行一次瀏覽器的重定向。

(9)

Seam提供了大量的 內(nèi)置組件(built-in components) 來協(xié)助解決那些經(jīng)常遇到的問題。 用 FacesMessages 組件就可很容易地來顯示模板化的錯誤或成功的消息。 內(nèi)置的Seam組件還可由注入或通過調(diào)用 instance() 方法來獲取。

這次我們并沒有顯式指定 @Scope,若沒有顯式指定時(shí),每個Seam 組件類型就使用其默認(rèn)的作用域。對于無狀態(tài)的會話Bean, 其默認(rèn)的作用域就是無狀態(tài)的上下文。實(shí)際上 所有的 無狀態(tài)的會話Bean都屬于無狀態(tài)的上下文。

會話Bean的動作監(jiān)聽器在此小應(yīng)用中履行了業(yè)務(wù)和持久化邏輯。在更復(fù)雜的應(yīng)用中,我們可能要將代碼分層并重構(gòu)持久化邏輯層成 專用數(shù)據(jù)存取組件,這很容易做到。但請注意Sean并不強(qiáng)制你在應(yīng)用分層時(shí)使用某種特定的分層策略。

此外,也請注意我們的SessionBean會同步訪問與web請求相關(guān)聯(lián)的上下文(比如在 User 對象中的表單的值),狀態(tài)會被保持在事務(wù)型的資源里(EntityManager 對象)。 這是對傳統(tǒng)J2EE的體系結(jié)構(gòu)的突破。再次說明,如果你習(xí)慣于傳統(tǒng)J2EE的分層,也可以在你的Seam應(yīng)用實(shí)行。但是對于許多的應(yīng)用,這是明顯的沒有必要 。

1.2.1.3. 會話Bean的本地接口:Register.java

很自然,我們的會話Bean需要一個本地接口。

Example 1.3. 

@Localpublic interface Register{   public String register();}

所有的Java代碼就這些了,現(xiàn)在去看一下部署描述文件。

1.2.1.4. Seam組件部署描述文件:components.xml

如果你此前曾接觸過許多的Java框架,你就會習(xí)慣于將所有的組件類放在某種XML文件中來聲明,那些文件就會隨著項(xiàng)目的不斷成熟而不斷加大到最終到不可收拾的地步。 對于Seam應(yīng)用,你盡可放心,因?yàn)樗⒉灰髴?yīng)用組件都要有相應(yīng)的XML。大部份的Seam應(yīng)用要求非常少量的XML即可,且XML文件大小不會隨著項(xiàng)目的增大而快速增長。

無論如何,若能為 某些 組件(特別是Seam內(nèi)置組件)提供某些 外部配置往往是有用的。這樣一來,我們就有幾個選擇, 但最靈活的選擇還是使用位于 WEB-INF 目錄下的 components.xml配置文件。 我們將用 components.xml 文件來演示Seam怎樣在JNDI中找到EJB組件:

Example 1.4. 

<components xmlns="http://jboss.com/products/seam/components"            xmlns:core="http://jboss.com/products/seam/core">     <core:init jndi-pattern="@jndiPattern@"/></components>

此代碼配置了Seam內(nèi)置組件 org.jboss.seam.core.init 的 jndiPattern 屬性。這里需要奇怪的@符號是因?yàn)锳NT腳本會在部署應(yīng)用時(shí)將正確的JNDI語法在標(biāo)記處自動填補(bǔ)

1.2.1.5. Web部署描述文件:web.xml

我們將以WAR的形式來部署此小應(yīng)用的表示層,因此需要web部署描述文件。

Example 1.5. 

<?xml version="1.0" encoding="UTF-8"?><web-app version="2.5"    xmlns="http://java.sun.com/xml/ns/javaee"    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee                        http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">    <!-- 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></web-app>

此 web.xml 文件配置了Seam和JSF。所有Seam應(yīng)用中的配置與此處的配置基本相同。

1.2.1.6. JSF配置:faces-config.xml

絕大多數(shù)的Seam應(yīng)用將JSF來作為表示層。因而我們通常需要 faces-config.xml。SEAM將用Facelet定義視圖表現(xiàn)層,所以我們需要告訴JSF用Facelet作為它的模板引擎。

Example 1.6. 

<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE faces-configPUBLIC "-//Sun Microsystems, Inc.//DTD JavaServer Faces Config 1.0//EN"                            "http://java.sun.com/dtd/web-facesconfig_1_0.dtd"><faces-config>    <!-- A phase listener is needed by all Seam applications -->    <lifecycle>        <phase-listener>org.jboss.seam.jsf.SeamPhaseListener</phase-listener>    </lifecycle></faces-config>

注意我們不需要申明任何JSF managed Bean!因?yàn)槲覀兯械膍anaged Bean都是通過經(jīng)過注釋的Seam組件。所以在Seam的應(yīng)用中,faces-config.xml比原始的JSF更少用到。

實(shí)際上,一旦你把所有的基本描述文件配置完畢,你所需寫的 唯一類型的 XML文件就是導(dǎo)航規(guī)則及可能的jBPM流程定義。對于Seam而言, 流程(process flow) 及 配置數(shù)據(jù) 是唯一真正屬于需要XML定義的。

在此簡單的示例中,因?yàn)槲覀儗⒁晥D頁面的ID嵌入到Action代碼中,所以我們甚至都不需要定義導(dǎo)航規(guī)則。

1.2.1.7. EJB部署描述文件:ejb-jar.xml

ejb-jar.xml 文件將 SeamInterceptor 綁定到壓縮包中所有的會話Bean上,以此實(shí)現(xiàn)了Seam與EJB3的整合。

<ejb-jar xmlns="http://java.sun.com/xml/ns/javaee"         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/ejb-jar_3_0.xsd"         version="3.0">   <interceptors>     <interceptor>       <interceptor-class>org.jboss.seam.ejb.SeamInterceptor</interceptor-class>     </interceptor>   </interceptors>   <assembly-descriptor>      <interceptor-binding>         <ejb-name>*</ejb-name>         <interceptor-class>org.jboss.seam.ejb.SeamInterceptor</interceptor-class>      </interceptor-binding>   </assembly-descriptor></ejb-jar>

1.2.1.8. EJB持久化部署描述文件:persistence.xml

persistence.xml 文件告訴EJB的持久化層在哪找到數(shù)據(jù)源,該文件也含有一些廠商特定的設(shè)定。此例在程序啟動時(shí)自動創(chuàng)建數(shù)據(jù)庫Schema。

<?xml version="1.0" encoding="UTF-8"?><persistence xmlns="http://java.sun.com/xml/ns/persistence"             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"             xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"             version="1.0">    <persistence-unit name="userDatabase">      <provider>org.hibernate.ejb.HibernatePersistence</provider>      <jta-data-source>java:/DefaultDS</jta-data-source>      <properties>         <property name="hibernate.hbm2ddl.auto" value="create-drop"/>      </properties>    </persistence-unit></persistence>

1.2.1.9. 視圖:register.xhtml 和 registered.xhtml

對于Seam應(yīng)用的視圖可由任意支持JSF的技術(shù)來實(shí)現(xiàn)。在此例中,我們使用了JSP,因?yàn)榇蠖鄶?shù)的開發(fā)人員都很熟悉, 且這里并沒有其它太多的要求。(我們建議你在實(shí)際開發(fā)中使用Facelets)。

Example 1.7. 

<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %><%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %><%@ taglib uri="http://jboss.com/products/seam/taglib" prefix="s" %><html> <head>  <title>Register New User</title> </head> <body>  <f:view>   <h:form>     <table border="0">       <s:validateAll>         <tr>           <td>Username</td>           <td><h:inputText value="#{user.username}"/></td>         </tr>         <tr>           <td>Real Name</td>           <td><h:inputText value="#{user.name}"/></td>         </tr>         <tr>           <td>Password</td>           <td><h:inputSecret value="#{user.password}"/></td>         </tr>       </s:validateAll>     </table>     <h:messages/>     <h:commandButton type="submit" value="Register" action="#{register.register}"/>   </h:form>  </f:view> </body></html>

這里的 <s:validateAll>標(biāo)簽是Seam特有的。 該JSF組件告訴JSF讓它用實(shí)體Bean中所指定的Hibernat驗(yàn)證器注解來驗(yàn)證所有包含輸入的字段。

Example 1.8. 

<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %><%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %><html> <head>  <title>Successfully Registered New User</title> </head> <body>  <f:view>    Welcome, <h:outputText value="#{user.name}"/>,    you are successfully registered as <h:outputText value="#{user.username}"/>.  </f:view> </body></html>

這是個極其普通的使用JSF組件的JSP頁面,與Seam毫無相干。

1.2.1.10. EAR部署描述文件:application.xml

最后,因?yàn)槲覀兊膽?yīng)用是要部署成EAR的,因此我們也需要部署描述文件。

Example 1.9. 

<?xml version="1.0" encoding="UTF-8"?><application xmlns="http://java.sun.com/xml/ns/javaee"             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"             xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/application_5.xsd"             version="5">    <display-name>Seam Registration</display-name>    <module>        <web>            <web-uri>jboss-seam-registration.war</web-uri>            <context-root>/seam-registration</context-root>        </web>    </module>    <module>        <ejb>jboss-seam-registration.jar</ejb>    </module>    <module>        <java>jboss-seam.jar</java>    </module>    <module>        <java>el-api.jar</java>    </module>    <module>        <java>el-ri.jar</java>    </module></application>

此部署描述文件聯(lián)接了EAR中的所有模塊,并把Web應(yīng)用綁定到此應(yīng)用的首頁 /seam-registration。

至此,我們了解了整個應(yīng)用中 所有的 部署描述文件!

1.2.2. 工作原理

當(dāng)提交表單時(shí),JSF請求Seam來解析名為 user 的變量。由于還沒有值綁定到 user 上(在任意的Seam上下文中), Seam就會實(shí)例化 user組件,接著把它保存在Seam會話上下文后,然后將 User 實(shí)體Bean實(shí)例返回給JSF。

表單輸入的值將由在 User 實(shí)體中所指定的Hibernate驗(yàn)證器來驗(yàn)證。 若有非法輸入,JSF就重新顯示當(dāng)前頁面。否則,JSF就將輸入值綁定到 User 實(shí)體Bean的字段上。

接著,JSF請求Seam來解析變量 register。 Seam在無狀態(tài)上下文中找到 RegisterAction 無狀態(tài)的會話Bean并把它返回。JSF隨之調(diào)用 register() 動作監(jiān)聽器方法。

Seam攔截方法調(diào)用并在繼續(xù)調(diào)用之前從Seam會話上下文注入 User 實(shí)體。

register() 方法檢查所輸入用戶名的用戶是否已存在。 若存在該用戶名,則錯誤消息進(jìn)入 facesmessages 組件隊(duì)列,返回?zé)o效結(jié)果并觸發(fā)瀏覽器重顯頁面。facesmessages 組件嵌在消息字符串的JSF表達(dá)式,并將JSF facesmessage 添加到視圖中。

若輸入的用戶不存在,"/registered.jsp" 輸出就會將瀏覽器重定向到 registered.jsp 頁。 當(dāng)JSF來渲染頁面時(shí),它請求Seam來解析名為 user 的變量,并使用從Seam會話作用域返回的User 實(shí)體的屬性值。

1.3. Seam中的可點(diǎn)擊列表:消息示例

在幾乎所有的在線應(yīng)用中都免不了將搜索結(jié)果顯示成可點(diǎn)擊的列表。 因此Sean在JSF層之上提供了特殊的功能,使得我們很容易用EJB-QL或HQL來查詢數(shù)據(jù)并用JSF <h:dataTable> 將查詢結(jié)果顯示成可點(diǎn)擊的列表。我們將在接下的例子中演示這一功能。

1.3.1. 理解代碼

此消息示例中有一個實(shí)體Bean,Message,一個會話Bean MessageListBean 及一個JSP頁面。

1.3.1.1. 實(shí)體Bean:Message.java

Message 實(shí)體定義了消息的title,text,date和time以及該消息是否已讀的標(biāo)志:

Example 1.10. 

@Entity@Name("message")@Scope(EVENT)public class Message implements Serializable{   private Long id;   private String title;   private String text;   private boolean read;   private Date datetime;   @Id @GeneratedValue   public Long getId() {      return id;   }   public void setId(Long id) {      this.id = id;   }   @NotNull @Length(max=100)   public String getTitle() {      return title;   }   public void setTitle(String title) {      this.title = title;   }   @NotNull @Lob   public String getText() {      return text;   }   public void setText(String text) {      this.text = text;   }   @NotNull   public boolean isRead() {      return read;   }   public void setRead(boolean read) {      this.read = read;   }   @NotNull   @Basic @Temporal(TemporalType.TIMESTAMP)   public Date getDatetime() {      return datetime;   }   public void setDatetime(Date datetime) {      this.datetime = datetime;   }}

1.3.1.2. 有狀態(tài)的會話Bean:MessageManagerBean.java

如此前的例子,會話Bean MessageManagerBean 用來給表單中的兩個按鈕定義個動作監(jiān)聽器方法, 其中的一個按鈕用來從列表中選擇消息,并顯示該消息。而另一個按鈕則用來刪除一條消息,除此之外,就沒什么特別之處了。

在用戶第一次瀏覽消息頁面時(shí),MessageManagerBean 會話Bean也負(fù)責(zé)抓取消息列表,考慮到用戶可能以多種方式來瀏覽該頁面,他們也有可能不是由JSF動作來完成,比如用戶可能將該頁加入收藏夾。 因此抓取消息列表發(fā)生在Seam的工廠方法中,而不是在動作監(jiān)聽器方法中。

之所以將此會話Bean設(shè)為有狀態(tài)的,是因?yàn)槲覀兿朐诓煌姆?wù)器請求間緩存此消息列表。

Example 1.11. 

@Stateful@Scope(SESSION)@Name("messageManager")public class MessageManagerBean implements Serializable, MessageManager{   @DataModel                                                                            (1)   private List<Message> messageList;   @DataModelSelection                                                                   (2)   @Out(required=false)                                                                  (3)   private Message message;   @PersistenceContext(type=EXTENDED)                                                    (4)   private EntityManager em;   @Factory("messageList")                                                               (5)   public void findMessages()   {      messageList = em.createQuery("from Message msg order by msg.datetime desc").getResultList();   }   public void select()                                                                  (6)   {      message.setRead(true);   }   public void delete()                                                                  (7)   {      messageList.remove(message);      em.remove(message);      message=null;   }   @Remove @Destroy                                                                      (8)   public void destroy() {}}
(1)

注解 @DataModel 暴露了 java.util.List 類型的屬性給JSF頁面來作為 javax.faces.model.DataModel 的實(shí)例。 這允許我們在JSF <h:dataTable>的每一行中能使用可點(diǎn)擊列表。在此例中,DataModel 可在變量名為 messageList 的會話上下文中被使用。

(2)

@DataModelSelection 注解告訴了Seam來注入 List 元素到相應(yīng)的被點(diǎn)擊鏈接。

(3)

注解 @Out 直接暴露了被選中的值給頁面。 這樣一來,每次可點(diǎn)擊列表一旦被選中,Message 就被會注入給有狀態(tài)Bean的屬性,緊接著 向外注入(outjected)給變量名為message 的事件上下文的屬性。

(4)

此有狀態(tài)Bean有個EJB3的 擴(kuò)展持久化上下文(extended persistence context)。只要Bean存在,查詢中獲取的消息就會保留在受管理的狀態(tài)中。 這樣一來,此后對有狀態(tài)Bean的所有方法調(diào)用勿需顯式調(diào)用 EntityManager 就可更新這些消息了。

(5)

當(dāng)我們第一次瀏覽JSP頁面時(shí),messageList 上下文變量尚未被初始化,@Factory 注解告訴Seam來創(chuàng)建 MessageManagerBean 的實(shí)例并調(diào)用 findMessages() 方法來初始化上下文變量。 我們把 findMessages() 當(dāng)作 messages 的 工廠方法。

(6)

select() 將選中的 Message 標(biāo)為已讀,并同時(shí)更新數(shù)據(jù)庫。

(7)

delete() 動作監(jiān)聽器方法將選中的 Message 從數(shù)據(jù)庫中刪除。

(8)

對于每個有狀態(tài)的會話Bean,Seam組件的所有方法中 必須 有一不帶參數(shù)的方法被標(biāo)為 @Remove @Destroy 以確保在Seam的上下文結(jié)束時(shí)刪除有狀態(tài)Bean,并同時(shí)清除所有服務(wù)器端的狀態(tài)。

請注意,這是個會話作用域的Seam組件。它與用戶登入會話相關(guān)聯(lián),并且登入會話的所有請求共享同一個組件的實(shí)例。 (在Seam的應(yīng)用中,我們通常使用會話作用域的組件。)

1.3.1.3. 會話Bean的本地接口:MessageManager.java

當(dāng)然,每個會話Bean都有個業(yè)務(wù)接口。

@Localpublic interface MessageManager{   public void findMessages();   public void select();   public void delete();   public void destroy();}

從現(xiàn)在起,我們在示例代碼中將不再對本地接口作特別的說明。

由于XML文件與此前的示例幾乎都一樣,因此我們略過了 components.xmlpersistence.xml、 web.xml、ejb-jar.xml、faces-config.xml 及application.xml 的細(xì)節(jié),直接來看一下JSP。

1.3.1.4. 視圖:messages.jsp

JSP頁面就是直接使用JSF <h:dataTable> 的組件,并沒有與Seam有什么關(guān)系。

Example 1.12. 

<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %><%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %><html> <head>  <title>Messages</title> </head> <body>  <f:view>   <h:form>     <h2>Message List</h2>     <h:outputText value="No messages to display" rendered="#{messageList.rowCount==0}"/>     <h:dataTable var="msg" value="#{messageList}" rendered="#{messageList.rowCount>0}">        <h:column>           <f:facet name="header">              <h:outputText value="Read"/>           </f:facet>           <h:selectBooleanCheckbox value="#{msg.read}" disabled="true"/>        </h:column>        <h:column>           <f:facet name="header">              <h:outputText value="Title"/>           </f:facet>           <h:commandLink value="#{msg.title}" action="#{messageManager.select}"/>        </h:column>        <h:column>           <f:facet name="header">              <h:outputText value="Date/Time"/>           </f:facet>           <h:outputText value="#{msg.datetime}">              <f:convertDateTime type="both" dateStyle="medium" timeStyle="short"/>           </h:outputText>        </h:column>        <h:column>           <h:commandButton value="Delete" action="#{messageManager.delete}"/>        </h:column>     </h:dataTable>     <h3><h:outputText value="#{message.title}"/></h3>     <div><h:outputText value="#{message.text2}"/></div>   </h:form>  </f:view> </body></html>

1.3.2. 工作原理

當(dāng)我們首次瀏覽 messages.jsp 頁面時(shí),無論是否由回傳(postback)的JSF(頁面請求)或?yàn)g覽器直接的GET請求(非頁面請求),此JSP頁面將設(shè)法解析 messagelist 上下文變量。 由于上下文變量尚未被初始化,因此Seam將調(diào)用工廠方法 findmessages(),該方法執(zhí)行了一次數(shù)據(jù)庫查詢并導(dǎo)致 DataModel 被向外注入。 DataModel 提供了渲染 <h:dataTable> 所需的行數(shù)據(jù)。

當(dāng)用戶點(diǎn)擊 <h:commandLink> 時(shí),JSF就調(diào)用 Select() 動作監(jiān)聽器。 Seam攔截此調(diào)用并將所選行的數(shù)據(jù)注入給 messageManager 組件的 message 屬性。 而動作監(jiān)聽器將所選定的 Message標(biāo)為已讀。在此調(diào)用結(jié)束時(shí),Seam向外注入所選定的 Message 給名為 message 的變量。 接著,EJB容器提交事務(wù),將 Message 的已讀標(biāo)記寫入數(shù)據(jù)庫。 最后,該網(wǎng)頁重新渲染,再次顯示消息列表,并在列表下方顯示所選消息的內(nèi)容。

如果用戶點(diǎn)擊了 <h:commandButton>,JSF就調(diào)用 delete() 動作監(jiān)聽器。 Seam攔截此調(diào)用并將所選行的數(shù)據(jù)注入給 messageManager 組件的 message 屬性。 觸發(fā)動作監(jiān)聽器,將選定的Message 從列表中刪除并同時(shí)在 EntityManager 中調(diào)用 remove() 方法。在此調(diào)用的最后,Seam刷新 messageList 上下文變量并清除名為 message 的上下文變量。 接著,EJB容器提交事務(wù),將 Message 從數(shù)據(jù)庫中刪除。最后,該網(wǎng)頁重新渲染,再次顯示消息列表。

1.4. Seam和jBPM:待辦事項(xiàng)列表(todo list)示例

jBPM提供了先進(jìn)的工作流程和任務(wù)管理的功能。為了體驗(yàn)一下jBPM是如何與Seam集成在一起工作的,在此將給你一個簡單的管理“待辦事項(xiàng)列表”的應(yīng)用。由于管理任務(wù)列表等功能是jBPM的核心功能,所以在此例中只用了很少的Java代碼。

1.4.1. 理解代碼

這個例子的核心是jBPM的流程定義(process definition)。此外,還有兩個JSP頁面和兩個簡單的JavaBeans(由于他們不用訪問數(shù)據(jù)庫,或有其它事務(wù)相關(guān)的行為,因此并沒有用會話Bean)。讓我們先從流程定義開始:

Example 1.13. 

<process-definition name="todo">   <start-state name="start">                                                            (1)      <transition to="todo"/>   </start-state>   <task-node name="todo">                                                               (2)      <task name="todo" description="#{todoList.description}">                           (3)         <assignment actor-id="#{actor.id}"/>                                            (4)      </task>      <transition to="done"/>   </task-node>   <end-state name="done"/>                                                              (5)</process-definition>
(1)

節(jié)點(diǎn) <start-state> 代表流程的邏輯開始。一旦流程開始時(shí),它就立即轉(zhuǎn)入 todo節(jié)點(diǎn)。

(2)

<task-node> 節(jié)點(diǎn)代表 等待狀態(tài),就是在執(zhí)行業(yè)務(wù)流程暫停時(shí),等待一個或多個未完成的任務(wù)。

(3)

<task> 元素定義了用戶需要完成的任務(wù)。 由于在這個節(jié)點(diǎn)只有定義了一個任務(wù),當(dāng)它完成,或恢復(fù)執(zhí)行時(shí)我們就轉(zhuǎn)入結(jié)束狀態(tài)。 此任務(wù)從Seam中名為 todolist 的組件(JavaBeans之一)獲得任務(wù)description。

(4)

任務(wù)在創(chuàng)建時(shí)就會被分配給一個用戶或一組用戶時(shí)。在此示例中,任務(wù)是分配給當(dāng)前用戶,該用戶從一個內(nèi)置的名為 actor 的Seam組件中獲得。任何Seam組件都可用來執(zhí)行任務(wù)指派。

(5)

<end-state>節(jié)點(diǎn)定義業(yè)務(wù)流程的邏輯結(jié)束。當(dāng)執(zhí)行到達(dá)這個節(jié)點(diǎn)時(shí),流程實(shí)例就要被銷毀。

如果我們用jBossIDE所提供的流程定義編輯器來查看此流程定義,那它就會是這樣:

這個文檔將我們的 業(yè)務(wù)流程 定義成節(jié)點(diǎn)圖。 這可能是最常見的業(yè)務(wù)流程:只有一個 任務(wù) 被執(zhí)行,當(dāng)這項(xiàng)任務(wù)完成之后,業(yè)務(wù)流程就結(jié)束了。

第一個JavaBean處理登入界面 login.jsp。 它的工作就是用 actor 組件初始化jBPM用戶id(在實(shí)際的應(yīng)用中,它也需要驗(yàn)證用戶。)

Example 1.14. 

@Name("login")public class Login {   @In   private Actor actor;   private String user;   public String getUser() {      return user;   }   public void setUser(String user) {      this.user = user;   }   public String login()   {      actor.setId(user);      return "/todo.jsp";   }}

在此我們使用了 @In 來將actor屬性值注入到Seam內(nèi)置的 Actor 組件。

JSP頁面本身并沒有什么特別之處:

Example 1.15. 

<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h"%><%@ taglib uri="http://java.sun.com/jsf/core" prefix="f"%><html><head><title>Login</title></head><body><h1>Login</h1><f:view>    <h:form>      <div>        <h:inputText value="#{login.user}"/>        <h:commandButton value="Login" action="#{login.login}"/>      </div>    </h:form></f:view></body></html>

第二個JavaBean負(fù)責(zé)啟動業(yè)務(wù)流程實(shí)例及結(jié)束任務(wù)。

Example 1.16. 

@Name("todoList")public class TodoList {   private String description;   public String getDescription()                                                        (1)   {      return description;   }   public void setDescription(String description) {      this.description = description;   }   @CreateProcess(definition="todo")                                                     (2)   public void createTodo() {}   @StartTask @EndTask                                                                   (3)   public void done() {}}
(1)

description屬性從JSP頁接受用戶輸入,并將它暴露給流程定義,這樣就可讓Seam來設(shè)定任務(wù)的descrption。

(2)

Seam的 @CreateProcess 注解為指定名稱的流程定義創(chuàng)建了一個新的jBPM流程實(shí)例。

(3)

Seam的 @StartTask 注解用來啟動任務(wù),@EndTask 用來結(jié)束任務(wù),并允許恢復(fù)執(zhí)行業(yè)務(wù)流程。

在實(shí)際的應(yīng)用中,@StartTask 及 @EndTask 不會出現(xiàn)在同一個方法中,因?yàn)闉榱送瓿扇蝿?wù),通常用應(yīng)用中有許多工作要做。

最后,該應(yīng)用的主要內(nèi)容在 todo.jsp 中:

Example 1.17. 

<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %><%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %><%@ taglib uri="http://jboss.com/products/seam/taglib" prefix="s" %><html><head><title>Todo List</title></head><body><h1>Todo List</h1><f:view>   <h:form id="list">      <div>         <h:outputText value="There are no todo items." rendered="#{empty taskInstanceList}"/>         <h:dataTable value="#{taskInstanceList}" var="task" rendered="#{not empty taskInstanceList}">            <h:column>                <f:facet name="header">                    <h:outputText value="Description"/>                </f:facet>                <h:inputText value="#{task.description}"/>            </h:column>            <h:column>                <f:facet name="header">                    <h:outputText value="Created"/>                </f:facet>                <h:outputText value="#{task.taskMgmtInstance.processInstance.start}">                    <f:convertDateTime type="date"/>                </h:outputText>            </h:column>            <h:column>                <f:facet name="header">                    <h:outputText value="Priority"/>                </f:facet>                <h:inputText value="#{task.priority}" style="width: 30"/>            </h:column>            <h:column>                <f:facet name="header">                    <h:outputText value="Due Date"/>                </f:facet>                <h:inputText value="#{task.dueDate}" style="width: 100">                    <f:convertDateTime type="date" dateStyle="short"/>                </h:inputText>            </h:column>            <h:column>                <s:button value="Done" action="#{todoList.done}" taskInstance="#{task}"/>            </h:column>         </h:dataTable>      </div>      <div>      <h:messages/>      </div>      <div>         <h:commandButton value="Update Items" action="update"/>      </div>   </h:form>   <h:form id="new">      <div>         <h:inputText value="#{todoList.description}"/>         <h:commandButton value="Create New Item" action="#{todoList.createTodo}"/>      </div>   </h:form></f:view></body></html>

讓我們對此逐一加以說明。

該JSP頁面將從Seam內(nèi)置組件 taskInstanceList 獲得的任務(wù)渲染成任務(wù)列表,此列表在JSF表單內(nèi)被定義。

<h:form id="list">   <div>      <h:outputText value="There are no todo items." rendered="#{empty taskInstanceList}"/>      <h:dataTable value="#{taskInstanceList}" var="task" rendered="#{not empty taskInstanceList}">         ...      </h:dataTable>   </div></h:form>

列表中的每個元素就是一個jBPM類 taskinstance 的實(shí)例。 以下代碼簡單地展示了列表中每一任務(wù)的有趣特性。為了讓用戶能更改description、priority及due date的值,我們使用了輸入控件。

<h:column>    <f:facet name="header">       <h:outputText value="Description"/>    </f:facet>    <h:inputText value="#{task.description}"/></h:column><h:column>    <f:facet name="header">        <h:outputText value="Created"/>    </f:facet>    <h:outputText value="#{task.taskMgmtInstance.processInstance.start}">        <f:convertDateTime type="date"/>    </h:outputText></h:column><h:column>    <f:facet name="header">        <h:outputText value="Priority"/>    </f:facet>    <h:inputText value="#{task.priority}" style="width: 30"/></h:column><h:column>    <f:facet name="header">        <h:outputText value="Due Date"/>    </f:facet>    <h:inputText value="#{task.dueDate}" style="width: 100">        <f:convertDateTime type="date" dateStyle="short"/>    </h:inputText></h:column>

該按鈕通過調(diào)用被注解為 @StartTask @EndTask 的動作方法來結(jié)束任務(wù)。它把任務(wù)id作為請求參數(shù)傳給Seam:

<h:column>    <s:button value="Done" action="#{todoList.done}" taskInstance="#{task}"/></h:column>

(請注意,這是在使用Seam seam-ui.jar 包中的JSF <s:button> 控件。)

這個按鈕是用來更新任務(wù)屬性。當(dāng)提交表單時(shí),Seam和jBPM將直接更改任務(wù)的持久化,不需要任何的動作監(jiān)聽器方法:

<h:commandButton value="Update Items" action="update"/>

第二個表單通過調(diào)用注解為 @CreateProcess的動作方法來創(chuàng)建新的項(xiàng)目(item)。

<h:form id="new">    <div>        <h:inputText value="#{todoList.description}"/>        <h:commandButton value="Create New Item" action="#{todoList.createTodo}"/>    </div></h:form>

這個例子還需要另外幾個文件,但它們只是標(biāo)準(zhǔn)的jBPM和Seam配置并不是很有趣。

1.4.2. 工作原理

待完成

1.5. Seam頁面流:猜數(shù)字范例

對有相對自由(特別)導(dǎo)航的Seam應(yīng)用程序而言,JSF/Seam導(dǎo)航規(guī)則是定義頁面流的一個完美的方法。 而對于那些帶有更多約束的導(dǎo)航,特別是帶狀態(tài)的用戶界面而言,導(dǎo)航規(guī)則反而使得系統(tǒng)流程變得難以理解。 要理解整個流程,你需要從視圖頁面、動作和導(dǎo)航規(guī)則里一點(diǎn)點(diǎn)把它拼出來。

Seam允許你使用一個jPDL流程定義來定義頁面流。下面這個簡單的猜數(shù)字范例將演示這一切是如何實(shí)現(xiàn)的。

1.5.1. 理解代碼

這個例子由一個JavaBean、三個JSP頁面和一個jPDL頁面流定義組成。讓我們從頁面流開始:

Example 1.18. 

<pageflow-definition name="numberGuess">   <start-page name="displayGuess" view-id="/numberGuess.jsp">      <redirect/>      <transition name="guess" to="evaluateGuess">          <action expression="#{numberGuess.guess}" />      </transition>                                                                      (1)   </start-page>                                                                         (2)                                                                                         (3)   <decision name="evaluateGuess" expression="#{numberGuess.correctGuess}">      <transition name="true" to="win"/>      <transition name="false" to="evaluateRemainingGuesses"/>   </decision>                                                                           (4)   <decision name="evaluateRemainingGuesses" expression="#{numberGuess.lastGuess}">      <transition name="true" to="lose"/>      <transition name="false" to="displayGuess"/>   </decision>   <page name="win" view-id="/win.jsp">      <redirect/>      <end-conversation />   </page>   <page name="lose" view-id="/lose.jsp">      <redirect/>      <end-conversation />   </page></pageflow-definition>
(1)

<page>元素定義了一個等待狀態(tài),在該狀態(tài)中系統(tǒng)顯示一個JSF視圖等待用戶輸入。 view-id與簡單JSF導(dǎo)航規(guī)則中的view id一樣。 redirect屬性告訴Seam在導(dǎo)航到頁面時(shí)使用post-then-redirect。(這會帶來友好的瀏覽器URL。)

(2)

<transition> 元素命名了一個JSF輸出。當(dāng)一個JSF動作導(dǎo)致那個輸出時(shí)會觸發(fā)轉(zhuǎn)換。 在任何jBPM轉(zhuǎn)換動作調(diào)用后,執(zhí)行會進(jìn)行到頁面流程圖的下一個節(jié)點(diǎn)。

(3)

一個轉(zhuǎn)換動作 <action> 就像JSF動作,不同的就是它只發(fā)生在一個jBPM轉(zhuǎn)換發(fā)生時(shí)。 轉(zhuǎn)換動作能調(diào)用任何Seam組件。

(4)

<decision> 節(jié)點(diǎn)用來劃分頁面流,通過計(jì)算JSF EL表達(dá)式?jīng)Q定要執(zhí)行的下一個節(jié)點(diǎn)。

這個頁面流在JBossIDE頁面流編輯器里看上去是這個樣子的:

看過了頁面流,現(xiàn)在再來理解剩下的程序就變得十分簡單了!

這是應(yīng)用程序的主頁面numberGuess.jspx

Example 1.19. 

<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h"%><%@ taglib uri="http://java.sun.com/jsf/core" prefix="f"%><html><head><title>Guess a number...</title></head><body><h1>Guess a number...</h1><f:view>    <h:form>        <h:outputText value="Higher!" rendered="#{numberGuess.randomNumber>numberGuess.currentGuess}" />        <h:outputText value="Lower!" rendered="#{numberGuess.randomNumber<numberGuess.currentGuess}" />        <br />        I'm thinking of a number between <h:outputText value="#{numberGuess.smallest}" /> and        <h:outputText value="#{numberGuess.biggest}" />. You have        <h:outputText value="#{numberGuess.remainingGuesses}" /> guesses.        <br />        Your guess:        <h:inputText value="#{numberGuess.currentGuess}" id="guess" required="true">            <f:validateLongRange                maximum="#{numberGuess.biggest}"                minimum="#{numberGuess.smallest}"/>        </h:inputText>        <h:commandButton type="submit" value="Guess" action="guess" />        <br/>        <h:message for="guess" style="color: red"/>    </h:form></f:view></body></html>

請注意名為 guess 的命令按鈕是如何進(jìn)行轉(zhuǎn)換而不是直接調(diào)用一個動作的。

win.jspx 頁面的內(nèi)容是可想而知的:

Example 1.20. 

<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h"%><%@ taglib uri="http://java.sun.com/jsf/core" prefix="f"%><html><head><title>You won!</title></head><body><h1>You won!</h1><f:view>    Yes, the answer was <h:outputText value="#{numberGuess.currentGuess}" />.    It took you <h:outputText value="#{numberGuess.guessCount}" /> guesses.    Would you like to <a href="numberGuess.seam">play again</a>?  </f:view></body></html>

lose.jsp 也差不多(我就不重復(fù)復(fù)制/粘貼了)。最后,JavaBean Seam組件是這樣的:

Example 1.21. 

@Name("numberGuess")@Scope(ScopeType.CONVERSATION)public class NumberGuess {   private int randomNumber;   private Integer currentGuess;   private int biggest;   private int smallest;   private int guessCount;   private int maxGuesses;   @Create                                                                               (1)   @Begin(pageflow="numberGuess")                                                        (2)   public void begin()   {      randomNumber = new Random().nextInt(100);      guessCount = 0;      biggest = 100;      smallest = 1;   }   public void setCurrentGuess(Integer guess)   {      this.currentGuess = guess;   }   public Integer getCurrentGuess()   {      return currentGuess;   }   public void guess()   {      if (currentGuess>randomNumber)      {         biggest = currentGuess - 1;      }      if (currentGuess<randomNumber)      {         smallest = currentGuess + 1;      }      guessCount ++;   }   public boolean isCorrectGuess()   {      return currentGuess==randomNumber;   }   public int getBiggest()   {      return biggest;   }   public int getSmallest()   {      return smallest;   }   public int getGuessCount()   {      return guessCount;   }   public boolean isLastGuess()   {      return guessCount==maxGuesses;   }   public int getRemainingGuesses() {      return maxGuesses-guessCount;   }   public void setMaxGuesses(int maxGuesses) {      this.maxGuesses = maxGuesses;   }   public int getMaxGuesses() {      return maxGuesses;   }   public int getRandomNumber() {      return randomNumber;   }}
(1)

一開始,JSP頁面請求一個 numberGuess 組件,Seam會為該組件創(chuàng)建一個新的實(shí)例,并調(diào)用 @Create 方法,允許組件初始化自己。

(2)

@Begin 注解啟動了一個Seam 業(yè)務(wù)會話(conversation) (稍后詳細(xì)說明),并指定業(yè)務(wù)會話頁面流所要使用的頁面流定義。

如你所見,這個Seam組件是純業(yè)務(wù)邏輯的!它不需要知道任何關(guān)于用戶交互的東西。這點(diǎn)使得組件更易被復(fù)用。

1.5.2. 工作原理

TODO

1.6. 一個完整的Seam應(yīng)用程序:賓館預(yù)訂范例

1.6.1. 介紹

該系統(tǒng)是一個完整的賓館客房預(yù)訂系統(tǒng),它由下列功能組成:

  • 用戶注冊

  • 登錄

  • 注銷

  • 設(shè)置密碼

  • 搜索賓館

  • 選擇賓館

  • 客房預(yù)訂

  • 預(yù)訂確認(rèn)

  • 當(dāng)前預(yù)訂列表

應(yīng)用程序中使用了JSF、EJB 3.0和Seam,視圖部分結(jié)合了Facelets。也可以選擇使用JSF、Facelets、Seam、JavaBeans和Hibernate3。

在使用過一段時(shí)間后你會發(fā)現(xiàn)該應(yīng)用程序非常 健壯。你能使用回退按鈕、刷新瀏覽器、打開多個窗口, 或者鍵入各種無意義的數(shù)據(jù),會發(fā)現(xiàn)都很難讓它崩潰。你也許會想我們花了幾個星期測試修復(fù)該系統(tǒng)才達(dá)到了這個目標(biāo)。 事實(shí)卻不是這樣的,Seam的設(shè)計(jì)使你能夠用它方便地構(gòu)建健壯的web應(yīng)用程序,而且Seam還提供了很多以前需要通過編碼才能實(shí)現(xiàn)的健壯性。

在你瀏覽范例程序代碼研究它是如何運(yùn)行時(shí),注意觀察聲明式的狀態(tài)管理和集成的驗(yàn)證是如何被用來實(shí)現(xiàn)這種健壯性的。

1.6.2. 預(yù)訂系統(tǒng)概況

這個項(xiàng)目的結(jié)構(gòu)和上一個一樣,要安裝部署該應(yīng)用程序請參考Section 1.1, “試試看”。 當(dāng)應(yīng)用程序啟動后,可以通過 http://localhost:8080/seam-booking/ 進(jìn)行訪問。

只需要用9個類(加上6個Session Bean的本地接口)就能實(shí)現(xiàn)這個應(yīng)用程序。6個Session Bean動作監(jiān)聽器包括了以下功能的所有業(yè)務(wù)邏輯。

  • BookingListAction 獲得當(dāng)前登錄用戶的預(yù)訂列表。
  • ChangePasswordAction 修改當(dāng)前用戶的密碼。
  • HotelBookingAction 實(shí)現(xiàn)了應(yīng)用程序的核心功能:賓館客房搜索、選擇、預(yù)訂和預(yù)訂確認(rèn)。 這功能是以 業(yè)務(wù)對話(conversation) 形式實(shí)現(xiàn)的,所以它是整個程序中最有意思的一個類。
  • RegisterAction 注冊一個新用戶。

應(yīng)用程序的持久化模型由三個實(shí)體bean實(shí)現(xiàn)。

  • Hotel 是表示一個賓館的實(shí)體Bean
  • Booking 是表示一個預(yù)訂的實(shí)體Bean
  • User 是表示一個能夠進(jìn)行賓館預(yù)訂的用戶的實(shí)體Bean

1.6.3. 理解Seam業(yè)務(wù)對話(Conversation)

我們鼓勵您隨意瀏覽源代碼。在這個教程里我們將關(guān)注功能中的某一特定部分:賓館搜索、選擇、預(yù)訂和確認(rèn)。 從用戶的角度來看,從選擇賓館到確認(rèn)的每一步都是工作中的一個連續(xù)單元,屬于一個 業(yè)務(wù)對話。 然而搜索卻  是該對話的一部分。用戶能在不同瀏覽器標(biāo)簽頁中的相同搜索結(jié)果頁面中選擇多個賓館。

大多數(shù)Web應(yīng)用程序架構(gòu)沒有提供表示業(yè)務(wù)對話的一級構(gòu)件(first class construct)。這在管理與對話相關(guān)的狀態(tài)時(shí)帶來了很多麻煩。 通常情況下,Java的Web應(yīng)用程序結(jié)合兩種技術(shù)來應(yīng)對這一情況:一是將某些狀態(tài)丟入 HttpSession;二是將可持久化的狀態(tài)在每個請求(Request)后寫入數(shù)據(jù)庫,并在每個新請求的開始將之重建。

由于數(shù)據(jù)庫是最不可擴(kuò)展的一層,因此這么做往往導(dǎo)致完全無法接受的擴(kuò)展性低下。在每次請求時(shí)訪問數(shù)據(jù)庫所造成的額外流量和等待時(shí)間也是一個問題。 要降低冗余流量,Java應(yīng)用程序常引入一個(二級)數(shù)據(jù)緩存來保存被經(jīng)常訪問的數(shù)據(jù)。 然而這個緩存是很低效的,因?yàn)樗氖惴ㄊ腔贚RU(最近最少使用)策略,而不是基于用戶何時(shí)結(jié)束與該數(shù)據(jù)相關(guān)的工作。 此外,由于該緩存被許多并發(fā)事務(wù)共享,要保持緩存與數(shù)據(jù)庫的狀態(tài)一致,我們需要引入了一套完整的機(jī)制。

現(xiàn)在再讓我們考慮將狀態(tài)保存在 HttpSession 里。通過精心設(shè)計(jì)的編程,我們也許能控制session數(shù)據(jù)的大小。 但這遠(yuǎn)比聽起來要麻煩的多,因?yàn)閃eb瀏覽器允許特殊的非線性導(dǎo)航。 但假設(shè)我們在系統(tǒng)開發(fā)到一半的時(shí)候突然發(fā)現(xiàn)一個需求,它要求用戶可以擁有 多并發(fā)業(yè)務(wù)對話(我就碰到過)。 要開發(fā)一些機(jī)制,以分離與不同并發(fā)業(yè)務(wù)會話相關(guān)的session狀態(tài),并引入故障保護(hù),在用戶關(guān)閉瀏覽器窗口或標(biāo)簽頁時(shí)銷毀業(yè)務(wù)會話狀態(tài)。 這對普通人來說可不是一件輕松的事情(我就實(shí)現(xiàn)過兩次,一次是為一個客戶應(yīng)用程序,另一次是為Seam,幸好我是出了名的瘋子)。

現(xiàn)在提供一個更好的方法。

Seam引入了 對話上下文 來作為一級構(gòu)件。你能在其中安全地保存業(yè)務(wù)對話狀態(tài),它會保證狀態(tài)有一個定義良好的生命周期。 而且,你不用再不停地在應(yīng)用服務(wù)器和數(shù)據(jù)庫間傳遞數(shù)據(jù),因?yàn)闃I(yè)務(wù)對話上下文就是一個天然的緩存,用來緩存用戶的數(shù)據(jù)。

通常情況下,我們保存在業(yè)務(wù)對話上下文中的組件是有狀態(tài)的Session Bean。(我們也在其中保存實(shí)體Bean和JavaBeans。) 在Java社區(qū)中一直有一個謠傳,認(rèn)為有狀態(tài)的Session Bean是擴(kuò)展性的殺手。在1998年WebFoobar 1.0發(fā)布時(shí)的確如此。 但今天的情況已經(jīng)變了。像JBoss 4.0這樣的應(yīng)用服務(wù)器都有很成熟的機(jī)制處理有狀態(tài)Session Bean的狀態(tài)復(fù)制。 (例如,JBoss EJB3容器可以執(zhí)行很細(xì)致的復(fù)制,只復(fù)制那些屬性值被改變過的bean。) 請注意,所有那些傳統(tǒng)技術(shù)中關(guān)于有狀態(tài)Bean是低效的爭論也同樣發(fā)生在 HttpSession 上,所以說將狀態(tài)從業(yè)務(wù)層的有狀態(tài)Session Bean遷移到Web Session中以提高性能的做法毫無疑問是被誤導(dǎo)的。 不正確地使用有狀態(tài)的Bean,或者是將它們用在錯誤的地方上都會使應(yīng)用程序變得無法擴(kuò)展。 但這并不意味著你應(yīng)該 永遠(yuǎn)不要 使用它們。總之,Seam會告訴你一個安全使用的模型。歡迎來到2005年。

OK,不再多說了,話題回到這個指南上吧。

賓館預(yù)訂范例演示了不同作用域的有狀態(tài)組件是如何協(xié)同工作實(shí)現(xiàn)復(fù)雜的行為的。 它的主頁面允許用戶搜索賓館。搜索的結(jié)果被保存在Seam的session域中。 當(dāng)用戶導(dǎo)航到其中一個賓館時(shí),一個業(yè)務(wù)會話便開始了,一個業(yè)務(wù)會話域組件回調(diào)session域組件以獲得選中的賓館。

賓館預(yù)訂范例還演示了如何使用Ajax4JSF在不用手工編寫JavaScript的情況下實(shí)現(xiàn)富客戶端(Rich Client)行為。

搜索功能用了一個Session域的有狀態(tài)Session Bean來實(shí)現(xiàn),有點(diǎn)類似于我們在上面的消息列表范例里看到的那個Session Bean。

Example 1.22. 

@Stateful                                                                                (1)@Name("hotelSearch")@Scope(ScopeType.SESSION)@Restrict("#{identity.loggedIn}")                                                        (2)public class HotelSearchingAction implements HotelSearching{   @PersistenceContext   private EntityManager em;   private String searchString;   private int pageSize = 10;   private int page;   @DataModel   private List<Hotel> hotels;                                                           (3)   public String find()   {      page = 0;      queryHotels();      return "main";   }   public String nextPage()   {      page++;      queryHotels();      return "main";   }   private void queryHotels()   {      String searchPattern = searchString==null ? "%" : '%' + searchString.toLowerCase().replace('*', '%') + '%';      hotels = em.createQuery("select h from Hotel h where lower(h.name) like :search or lower(h.city) like :search or lower(h.zip) like :search or lower(h.address) like :search")            .setParameter("search", searchPattern)            .setMaxResults(pageSize)            .setFirstResult( page * pageSize )            .getResultList();   }   public boolean isNextPageAvailable()   {      return hotels!=null && hotels.size()==pageSize;   }   public int getPageSize() {      return pageSize;   }   public void setPageSize(int pageSize) {      this.pageSize = pageSize;   }   public String getSearchString()   {      return searchString;   }   public void setSearchString(String searchString)   {      this.searchString = searchString;   }   @Destroy @Remove   public void destroy() {}                                                              (4)}
(1)

EJB標(biāo)準(zhǔn)中的 @Stateful 注解表明這個類是一個有狀態(tài)的Session Bean。它們的默認(rèn)作用域是業(yè)務(wù)對話上下文。

(2)

@Restrict注解給組件加上了一個安全限制。只有登錄過的用戶才能訪問該組件。安全章節(jié)中更詳細(xì)地討論了Seam的安全問題。

(3)

@DataModel 注解將一個 List 作為JSF ListDataModel 暴露出去。 這簡化了搜索界面的可單擊列表的實(shí)現(xiàn)。在這個例子中,賓館的列表是以名為 hotels 的 ListDataModel 業(yè)務(wù)對話變量暴露給頁面的。

(4)

EJB標(biāo)準(zhǔn)中的 @Remove 注解指定了一個有狀態(tài)的Session Bean應(yīng)該在注解的方法被調(diào)用后被刪除且其狀態(tài)應(yīng)該被銷毀。 在Seam里,所有有狀態(tài)的Session Bean都應(yīng)該定義一個標(biāo)有 @Destroy @Remove 的方法。 這是Seam在銷毀Session上下文時(shí)要調(diào)用的EJB刪除方法。實(shí)際上 @Destroy 注解更有用,因?yàn)樗茉赟eam上下文結(jié)束時(shí)被用來做各種各樣的清理工作。如果沒有一個 @Destroy @Remove 方法,那么狀態(tài)會泄露,你就會碰到性能上的問題。

應(yīng)用程序的主頁面是一個Facelets頁面。讓我們來看下與賓館搜索相關(guān)的部分:

Example 1.23. 

<div class="section"><h:form>  <span class="errors">    <h:messages globalOnly="true"/>  </span>  <h1>Search Hotels</h1>  <fieldset>     <h:inputText value="#{hotelSearch.searchString}" style="width: 165px;">        <a:support event="onkeyup" actionListener="#{hotelSearch.find}"                  (1)                   reRender="searchResults" />     </h:inputText>           <a:commandButton value="Find Hotels" action="#{hotelSearch.find}"                      styleClass="button" reRender="searchResults"/>           <a:status>                                                                          (2)        <f:facet name="start">           <h:graphicImage value="/img/spinner.gif"/>        </f:facet>     </a:status>     <br/>     <h:outputLabel for="pageSize">Maximum results:</h:outputLabel>      <h:selectOneMenu value="#{hotelSearch.pageSize}" id="pageSize">        <f:selectItem itemLabel="5" itemValue="5"/>        <f:selectItem itemLabel="10" itemValue="10"/>        <f:selectItem itemLabel="20" itemValue="20"/>     </h:selectOneMenu>  </fieldset></h:form></div><a:outputPanel id="searchResults">                                                       (3)  <div class="section">  <h:outputText value="No Hotels Found"                rendered="#{hotels != null and hotels.rowCount==0}"/>  <h:dataTable value="#{hotels}" var="hot" rendered="#{hotels.rowCount>0}">    <h:column>      <f:facet name="header">Name</f:facet>      #{hot.name}    </h:column>    <h:column>      <f:facet name="header">Address</f:facet>      #{hot.address}    </h:column>    <h:column>      <f:facet name="header">City, State</f:facet>      #{hot.city}, #{hot.state}, #{hot.country}    </h:column>    <h:column>      <f:facet name="header">Zip</f:facet>      #{hot.zip}    </h:column>    <h:column>      <f:facet name="header">Action</f:facet>      <s:link value="View Hotel" action="#{hotelBooking.selectHotel(hot)}"/>             (4)    </h:column>  </h:dataTable>  <s:link value="More results" action="#{hotelSearch.nextPage}"          rendered="#{hotelSearch.nextPageAvailable}"/>  </div></a:outputPanel>
(1)

Ajax4JSF的 <a:support> 標(biāo)簽允許一個JSF動作事件監(jiān)聽器在類似 onkeyup 這樣的JavaScript事件發(fā)生時(shí)被異步的 XMLHttpRequest 調(diào)用。 更棒的是,reRender 屬性讓我們可以在收到異步響應(yīng)時(shí)渲染一個JSF頁面的片段并執(zhí)行一個頁面的局部修改。

(2)

Ajax4JSF的 <a:status> 標(biāo)簽使我們能在等待異步請求返回時(shí)顯示一個簡單的動畫。

(3)

Ajax4JSF的 <a:outputPanel> 標(biāo)簽定義了一塊能被異步請求修改的頁面區(qū)域。

(4)

Seam的<s:link> 標(biāo)簽使我們能將一個JSF動作監(jiān)聽器附加在一個普通的(非JavaScript)HTML鏈接上。 用它取代標(biāo)準(zhǔn)JSF的 <h:commandLink> 的好處就是它在“在新窗口中打開”和“在新標(biāo)簽頁中打開”時(shí)仍然有效。 值得注意的另一點(diǎn)就是我們用了一個綁定了參數(shù)的方法:#{hotelBooking.selectHotel(hot)}。 在標(biāo)準(zhǔn)的統(tǒng)一EL中這是不允許的,但Seam對EL的擴(kuò)展進(jìn)行了擴(kuò)展,使表達(dá)式能夠支持帶參數(shù)的方法。

這個頁面根據(jù)我們的鍵入動態(tài)地顯示搜索結(jié)果,讓我們選擇一家賓館并將它傳給 HotelBookingAction 的 selectHotel() 方法,這個對象才是 真正 有趣的地方。

現(xiàn)在讓我們來看看賓館預(yù)定范例程序是如何使用一個對話域的有狀態(tài)的Session Bean的,這個Session Bean實(shí)現(xiàn)了業(yè)務(wù)會話相關(guān)持久化數(shù)據(jù)的天然緩存。 下面的代碼很長。但如果你把它理解為實(shí)現(xiàn)業(yè)務(wù)會話的多個步驟的一系列動作的話,它是不難理解的。我們把這個類當(dāng)作故事一樣從頭開始閱讀。

Example 1.24. 

@Stateful@Name("hotelBooking")@Restrict("#{identity.loggedIn}")public class HotelBookingAction implements HotelBooking{   @PersistenceContext(type=EXTENDED)                                                    (1)   private EntityManager em;   @In                                                                                   (2)   private User user;   @In(required=false) @Out   private Hotel hotel;   @In(required=false)   @Out(required=false)   private Booking booking;   @In   private FacesMessages facesMessages;   @In   private Events events;   @Logger   private Log log;   @Begin                                                                                (3)   public String selectHotel(Hotel selectedHotel)   {      hotel = em.merge(selectedHotel);      return "hotel";   }   public String bookHotel()   {      booking = new Booking(hotel, user);      Calendar calendar = Calendar.getInstance();      booking.setCheckinDate( calendar.getTime() );      calendar.add(Calendar.DAY_OF_MONTH, 1);      booking.setCheckoutDate( calendar.getTime() );      return "book";   }   public String setBookingDetails()   {      if (booking==null || hotel==null) return "main";      if ( !booking.getCheckinDate().before( booking.getCheckoutDate() ) )      {         facesMessages.add("Check out date must be later than check in date");         return null;      }      else      {         return "confirm";      }   }   @End                                                                                  (4)   public String confirm()   {      if (booking==null || hotel==null) return "main";      em.persist(booking);      facesMessages.add("Thank you, #{user.name}, your confimation number for #{hotel.name} is #{booking.id}");      log.info("New booking: #{booking.id} for #{user.username}");      events.raiseEvent("bookingConfirmed");      return "confirmed";   }   @End   public String cancel()   {      return "main";   }   @Destroy @Remove                                                                      (5)   public void destroy() {}}
(1)

這個bean使用EJB3的 擴(kuò)展持久化上下文,所以任意實(shí)體實(shí)例在整個有狀態(tài)Session Bean的生命周期中一直受到管理。

(2)

@Out 注解聲明了一個屬性值在方法調(diào)用后會 向外注入 到一個上下文變量中的。 在這個例子中,名為 hotel 的上下文變量會在每個動作監(jiān)聽器調(diào)用完成后被設(shè)置為 hotel 實(shí)例變量的值。

(3)

@Begin 注解表明被注解的方法開始一個 長期業(yè)務(wù)對話,因此當(dāng)前業(yè)務(wù)對話上下文在請求結(jié)束后不會被銷毀。相反,它會被關(guān)聯(lián)給當(dāng)前窗口的每次請求,在業(yè)務(wù)對話超時(shí)時(shí)或者一個 @End 方法被調(diào)用后銷毀。

(4)

@End 注解表明被注解的方法被用來結(jié)束一個長期業(yè)務(wù)對話,所以當(dāng)前業(yè)務(wù)對話上下文會在請求結(jié)束后被銷毀。

(5)

這個EJB刪除方法會在Seam銷毀業(yè)務(wù)對話上下文時(shí)被調(diào)用。不要忘記定義該方法!

HotelBookingAction 包含了實(shí)現(xiàn)選擇、預(yù)訂和預(yù)訂確認(rèn)的所有動作監(jiān)聽器方法,并在它的實(shí)例變量中保存與之相關(guān)的狀態(tài)。 我們認(rèn)為你一定會同意這個代碼比起獲取和設(shè)置 HttpSession的屬性來說要簡潔的多。

而且,一個用戶能在每個登錄Session中擁有多個獨(dú)立的業(yè)務(wù)對話。試試吧!登錄系統(tǒng),執(zhí)行搜索,在多個瀏覽器標(biāo)簽頁中導(dǎo)航到不同的賓館頁面。 你能在同一時(shí)間建立兩個不同的賓館預(yù)約。如果某個業(yè)務(wù)對話被閑置太長時(shí)間,Seam最終會判其超時(shí)并銷毀它的狀態(tài)。如果在結(jié)束業(yè)務(wù)對話后, 你按了退回按鈕回到那個會話的某一頁,嘗試執(zhí)行一個動作,Seam會檢測到那個業(yè)務(wù)對話已經(jīng)被結(jié)束了,并將你重定向到搜索頁面。

1.6.4. Seam的UI控制庫

如果你查看下預(yù)訂系統(tǒng)的WAR文件,你會在 WEB-INF/lib 目錄中找到 seam-ui.jar。 這個包里有許多Seam的JSF自定義控件。本應(yīng)用程序在從搜索界面導(dǎo)航到賓館頁面時(shí)使用了 <s:link>控件:

<s:link value="View Hotel" action="#{hotelBooking.selectHotel}"/>

這里的 <s:link> 允許我們在不打斷瀏覽器的“在新窗口打開”功能的情況下給HTML鏈接附加上一個動作監(jiān)聽器。 標(biāo)準(zhǔn)的JSF <h:commandLink> 無法在“在新窗口打開”的情況下正常工作。 稍后我們會看到 <s:link> 還能提供很多其他有用的特性,包括業(yè)務(wù)會話傳播規(guī)則。

賓館預(yù)訂系統(tǒng)里還用了些別的Seam和Ajax4JSF控件,特別是在 /book.xhtml 頁面里。我們在這里不深入討論這些控件,如果你想看懂這些代碼,請參考介紹Seam的JSF表單驗(yàn)證功能的章節(jié)。

1.6.5. Seam調(diào)試頁面

WAR文件還包括了 seam-debug.jar。如果把這個jar部屬在 WEB-INF/lib 下,結(jié)合Facelets,你能在 web.xml 或者 seam.properties 里設(shè)置如下的Seam屬性:

<context-param>    <param-name>org.jboss.seam.core.init.debug</param-name>    <param-value>true</param-value></context-param>

這樣就能訪問Seam調(diào)試頁面了。這個頁面可以讓你瀏覽并檢查任意與你當(dāng)前登錄Session相關(guān)的Seam上下文中的Seam組件。 只需瀏覽 http://localhost:8080/seam-booking/debug.seam 即可。

1.7. 一個使用Seam和jBPM的完整范例:DVD商店

DVD商店程序演示了如何在任務(wù)管理和頁面流中使用jBPM。

用戶界面應(yīng)用jPDL頁面流實(shí)現(xiàn)了搜索和購物車功能。

管理員界面使用jBPM來管理訂單的審批和送貨周期。業(yè)務(wù)流程可以通過選擇不同的流程定義實(shí)現(xiàn)動態(tài)改變。

TODO

dvdstore目錄。

1.8. 結(jié)合Seam和Hibernate的范例:Hibernate預(yù)訂系統(tǒng)

Hibernate預(yù)訂系統(tǒng)是之前客房預(yù)訂系統(tǒng)的另一個版本,它使用Hibernate和JavaBeans代替了會話Bean實(shí)現(xiàn)持久化。

TODO

hibernate目錄。

1.9. 一個RESTful的Seam應(yīng)用程序:Blog范例

Seam可以很方便地實(shí)現(xiàn)在服務(wù)器端保存狀態(tài)的應(yīng)用程序。 然而,服務(wù)器端狀態(tài)在有些情況下并不合適,特別是對那些用來提供內(nèi)容的功能。 針對這類問題,我們常需要讓用戶能夠收藏頁面,有一個相對無狀態(tài)的服務(wù)器,這樣一來能夠在任何時(shí)間通過書簽來訪問那些被收藏的頁面。 Blog范例演示了如何用Seam來實(shí)現(xiàn)一個RESTful的應(yīng)用程序。應(yīng)用程序中的每個頁面都能被收藏,包括搜索結(jié)果頁面。

Blog范例演示了“拉”風(fēng)格("pull"-style)的MVC,它不使用動作監(jiān)聽器方法來獲取數(shù)據(jù)和為視圖準(zhǔn)備數(shù)據(jù),而是視圖在被顯示時(shí)從組件中拉數(shù)據(jù)。

1.9.1. 使用“拉”風(fēng)格的MVC

從 index.xhtml Facelets頁面中取出的片斷顯示了blog的最近文章列表:

Example 1.25. 

<h:dataTable value="#{blog.recentBlogEntries}" var="blogEntry" rows="3">   <h:column>      <div class="blogEntry">         <h3>#{blogEntry.title}</h3>         <div>            <h:outputText escape="false"                  value="#{blogEntry.excerpt==null ? blogEntry.body : blogEntry.excerpt}"/>         </div>         <p>            <h:outputLink value="entry.seam" rendered="#{blogEntry.excerpt!=null}">               <f:param name="blogEntryId" value="#{blogEntry.id}"/>               Read more...            </h:outputLink>         </p>         <p>            [Posted on            <h:outputText value="#{blogEntry.date}">               <f:convertDateTime timeZone="#{blog.timeZone}" locale="#{blog.locale}" type="both"/>            </h:outputText>]                         <h:outputLink value="entry.seam">[Link]               <f:param name="blogEntryId" value="#{blogEntry.id}"/>            </h:outputLink>         </p>      </div>   </h:column></h:dataTable>

如果我們通過收藏夾訪問這個頁面,那么 <h:dataTable> 的數(shù)據(jù)是怎么被初始化的呢? 事實(shí)上,Blog 是延遲加載的,即在需要時(shí)才被名為 blog 的Seam組件“拉”出來。 這與傳統(tǒng)的基于動作的web框架(例如Struts)的控制流程正好相反。

Example 1.26. 

@Name("blog")@Scope(ScopeType.STATELESS)public class BlogService{   @In                                                                                   (1)   private EntityManager entityManager;   @Unwrap                                                                               (2)   public Blog getBlog()   {      return (Blog) entityManager.createQuery("from Blog b left join fetch b.blogEntries")            .setHint("org.hibernate.cacheable", true)            .getSingleResult();   }}
(1)

這個組件使用了一個 受Seam管理的持久化上下文(seam-managed persistence context)。 與我們看過的其他例子不同,這個持久化上下文是由Seam管理的,而不是EJB3容器。 持久化上下文貫穿于整個Web請求中,這使得在視圖里訪問未抓取的關(guān)聯(lián)數(shù)據(jù)時(shí)可以避免發(fā)生任何異常。

(2)

@Unwrap 注解告訴Seam將 Blog 而不是 BlogService 組件作為方法的返回值提供給客戶端。 這是Seam的 管理員組件模式(manager component pattern)

這些看起來已經(jīng)很不錯了,那如何來收藏諸如搜索結(jié)果頁這樣的表單提交結(jié)果頁面呢?

1.9.2. 可收藏的搜索結(jié)果頁面

Blog范例在每個頁面的右上方都有一個很小的表單,這個表單允許用戶搜索文章。 這是定義在一個名為 menu.xhtml 的文件里的,它被Facelets模板 template.xhtml 所引用:

Example 1.27. 

<div id="search">   <h:form>      <h:inputText value="#{searchAction.searchPattern}"/>      <h:commandButton value="Search" action="/search.xhtml"/>   </h:form></div>

要實(shí)現(xiàn)一個可收藏的搜索結(jié)果頁面,我們需要在處理搜索表單提交后執(zhí)行一個瀏覽器重定向。 因?yàn)槲覀冇肑SF視圖id作為動作輸出,所以Seam會在表單提交后自動重定向到該表單id。除此之外,我們也能像這樣來定義一個導(dǎo)航規(guī)則:

Example 1.28. 

<navigation-rule>   <navigation-case>      <from-outcome>searchResults</from-outcome>      <to-view-id>/search.xhtml</to-view-id>      <redirect/>   </navigation-case></navigation-rule>

然后表單看起來會是這個樣子的:

Example 1.29. 

<div id="search">   <h:form>      <h:inputText value="#{searchAction.searchPattern}"/>      <h:commandButton value="Search" action="searchResults"/>   </h:form></div>

在重定向時(shí),我們需要將表單的值作為請求參數(shù)包括進(jìn)來,得到的書簽URL會是這個樣子: http://localhost:8080/seam-blog/search.seam?searchPattern=seam。 JSF沒有為此提供一個簡單的途徑,但Seam卻有。我們能在 WEB-INF/pages.xml 中定義一個 頁面參數(shù)

Example 1.30. 

<pages>   <page view-id="/search.xhtml">      <param name="searchPattern" value="#{searchService.searchPattern}"/>   </page>   ...</pages>

這告訴Seam在重定向時(shí)將 #{searchService.searchPattern} 的值作為名字是 searchPattern 的請求參數(shù)包括進(jìn)去,并在顯示頁面前重新將這個值賦上。

重定向會把我們帶到 search.xhtml 頁面:

Example 1.31. 

<h:dataTable value="#{searchResults}" var="blogEntry">   <h:column>      <div>         <h:outputLink value="entry.seam">            <f:param name="blogEntryId" value="#{blogEntry.id}"/>            #{blogEntry.title}         </h:outputLink>         posted on         <h:outputText value="#{blogEntry.date}">            <f:convertDateTime timeZone="#{blog.timeZone}" locale="#{blog.locale}" type="both"/>         </h:outputText>      </div>   </h:column></h:dataTable>

此處同樣使用“拉”風(fēng)格的MVC來獲得實(shí)際搜索結(jié)果:

Example 1.32. 

@Name("searchService")public class SearchService{   @In   private EntityManager entityManager;   private String searchPattern;   @Factory("searchResults")   public List<BlogEntry> getSearchResults()   {      if (searchPattern==null)      {         return null;      }      else      {         return entityManager.createQuery("select be from BlogEntry be where lower(be.title) like :searchPattern or lower(be.body) like :searchPattern order by be.date desc")               .setParameter( "searchPattern", getSqlSearchPattern() )               .setMaxResults(100)               .getResultList();      }   }   private String getSqlSearchPattern()   {      return searchPattern==null ? "" : '%' + searchPattern.toLowerCase().replace('*', '%').replace('?', '_') + '%';   }   public String getSearchPattern()   {      return searchPattern;   }   public void setSearchPattern(String searchPattern)   {      this.searchPattern = searchPattern;   }}

1.9.3. 在RESTful應(yīng)用程序中使用“推”風(fēng)格("push"-style)的MVC

有些時(shí)候,用“推”風(fēng)格的MVC來處理RESTful頁面更有意義,為此Seam提供了 頁面動作。 Blog范例在文章頁面 entry.xhtml 里使用了頁面動作。請注意這里是故意這么做的,因?yàn)榇颂幨褂?#8220;拉”風(fēng)格的MVC會更容易。

entryAction 組件工作起來非常像傳統(tǒng)“推”風(fēng)格MVC的面向動作框架例如Struts里的動作類(action class):

Example 1.33. 

@Name("entryAction")@Scope(STATELESS)public class EntryAction{   @In(create=true)   private Blog blog;   @Out   private BlogEntry blogEntry;   public void loadBlogEntry(String id) throws EntryNotFoundException   {      blogEntry = blog.getBlogEntry(id);      if (blogEntry==null) throw new EntryNotFoundException(id);   }}

在 pages.xml 里也定義了頁面動作:

Example 1.34. 

<pages>   ...   <page view-id="/entry.xhtml" action="#{entryAction.loadBlogEntry(blogEntry.id)}">      <param name="blogEntryId" value="#{blogEntry.id}"/>   </page>   <page view-id="/post.xhtml" action="#{loginAction.challenge}"/>   <page view-id="*" action="#{blog.hitCount.hit}"/></pages>

范例中還將頁面動作運(yùn)用于一些其他的功能上 — 登錄和頁面訪問記數(shù)器。另外一點(diǎn)值得注意的是在頁面動作綁定中使用了一個參數(shù)。 這不是標(biāo)準(zhǔn)的JSF EL,是Seam為你提供的,你不僅能在頁面動作中使用它,還可以將它使用在JSF方法綁定中。

當(dāng) entry.xhtml 頁面被請求時(shí),Seam先為模型綁定上頁面參數(shù) blogEntryId,然后運(yùn)行頁面動作,該動作獲取所需的數(shù)據(jù) — blogEntry — 并將它放在Seam事件上下文中。最后顯示以下內(nèi)容:

Example 1.35. 

<div class="blogEntry">   <h3>#{blogEntry.title}</h3>   <div>      <h:outputText escape="false" value="#{blogEntry.body}"/>   </div>   <p>      [Posted on       <h:outputText value="#{blogEntry.date}">         <f:convertDateTime timezone="#{blog.timeZone}" locale="#{blog.locale}" type="both"/>      </h:outputText>]   </p></div>

如果在數(shù)據(jù)庫中沒有找到blog entry,就會拋出 EntryNotFoundException 異常。 我們想讓該異常引起一個404錯誤,而非505,所以為這個異常類添加個注解:

Example 1.36. 

@ApplicationException(rollback=true)@HttpError(errorCode=HttpServletResponse.SC_NOT_FOUND)public class EntryNotFoundException extends Exception{   EntryNotFoundException(String id)   {      super("entry not found: " + id);   }}

該范例的另一個實(shí)現(xiàn)在方法綁定中沒有使用參數(shù):

Example 1.37. 

@Name("entryAction")@Scope(STATELESS)public class EntryAction{   @In(create=true)   private Blog blog;   @In @Out   private BlogEntry blogEntry;   public void loadBlogEntry() throws EntryNotFoundException   {      blogEntry = blog.getBlogEntry( blogEntry.getId() );      if (blogEntry==null) throw new EntryNotFoundException(id);   }}
<pages>   ...   <page view-id="/entry.xhtml" action="#{entryAction.loadBlogEntry}">      <param name="blogEntryId" value="#{blogEntry.id}"/>   </page>   ...</pages>

你可以根據(jù)自己的喜好來選擇實(shí)現(xiàn)。

本站僅提供存儲服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊舉報(bào)。
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
Seam - 無縫集成 JSF,第 1 部分: 為 JSF 量身定做的應(yīng)用程序框架
深入淺出JBoss Seam
[轉(zhuǎn)]Java EE 5.0能取代Struts,Spring和Hibernate嗎?
JBoss、Geronimo及Tomcat比較分析-華軍資訊
表現(xiàn)層架構(gòu)的功能和特點(diǎn)
概述 - Velocity空間 - BlogJava
更多類似文章 >>
生活服務(wù)
分享 收藏 導(dǎo)長圖 關(guān)注 下載文章
綁定賬號成功
后續(xù)可登錄賬號暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服