alienj 发表于 2013-2-6 08:06:46

第1章 seam 教程(2)

第1章  seam 教程(2)

1.5. Seam 页面流:  numberguess例子
对Seam应用程序相对自由(特别)的导航而言,JSF/Seam导航控制来定义页面流是一个相当完美的方法。对于带有多约束形式的导航,尤其对更多有状态的用户界面,导航控制使得真正理解系统流变得困难。为了理解流,你需要将视图页面,动作和导航控制接合在一起。
 Seam允许你使用一个jPDL定义来定义页面流。这个简单的猜数例子显示了应该如何去做。
 
1.5.1. 理解代码
实现这个例子,使用了一个JavaBean,三个JSP页面,一个jPDL页面流定义文件。让我们开始用页面流:
 
Example 1.18.   pageflow.jpdl.xml
<pageflow-definition
       xmlns="http://jboss.com/products/seam/pageflow"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation=
           "http://jboss.com/products/seam/pageflow http://jboss.com/products/seam/pageflow-2.1.xsd"
       name="numberGuess">
  
   <start-page name="displayGuess" view-id="/numberGuess.jspx">         ⑴
      <redirect/> 
      <transition name="guess" to="evaluateGuess">        ⑵
 
         <action expression="#{numberGuess.guess}"/>        ⑶
      </transition>
      <transition name="giveup" to="giveup"/>
      <transition name="cheat" to="cheat"/>
   </start-page>
  
   <decision name="evaluateGuess" expression="#{numberGuess.correctGuess}"> ⑷
      <transition name="true" to="win"/>
      <transition name="false" to="evaluateRemainingGuesses"/>
   </decision>
  
   <decision name="evaluateRemainingGuesses" expression="#{numberGuess.lastGuess}">
      <transition name="true" to="lose"/>
      <transition name="false" to="displayGuess"/>
   </decision>
  
   <page name="giveup" view-id="/giveup.jspx">
      <redirect/>
      <transition name="yes" to="lose"/>
      <transition name="no" to="displayGuess"/>
   </page>
  
   <process-state name="cheat">
      <sub-process name="cheat"/>
      <transition to="displayGuess"/>
   </process-state>
 
   <page name="win" view-id="/win.jspx">
      <end-conversation/>
      <redirect/>
   </page>
  
   <page name="lose" view-id="/lose.jspx">
      <end-conversation/>
      <redirect/>
   </page>
  
</pageflow-definition>
 
⑴<page>元素定义了一个等待状态,系统显示一个特殊的JSF视图等待用户输入。view-id是和用在普通JSF导航控制中的视图id是相同的。当导航到该页面时,Redirect属性告诉Seam用“传递然后重定向”(post-then-redirect)(这导致友好的浏览器URLs)。
 
⑵<transition>元素指定一个JSF输出。当一个JSF动作导致输出时,转换被触发。在调用一些jBPM转换动作后,执行将会处理页面流图的下一个结点。
⑶一个转换<action>除它在一个jBPM转换发生时发生外,和一个      JSF动作完全一样。转换动作能调用任何Seam组件。
 
⑷一个<decision>节点产生页面流分枝,并且用一个JSF EL的值来决定下一个节点会被执行。
 
在 JBoss Developer Studio 页面流编辑器中看起来如下面的页面流结构:
http://img.blog.163.com/photo/J5ZAEUdVlItJ4dgLuokjrw==/904660575148654583.jpg
 
 
现在我们已了解了页面流,因而理解应用程序的其它部分是非常,非常容易的。
 
这是应用程序的主页,numberGuess.jspx:
Example 1.19.
<?xml version="1.0"?>
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page"
          xmlns:h="http://java.sun.com/jsf/html"
          xmlns:f="http://java.sun.com/jsf/core"
          xmlns:s="http://jboss.com/products/seam/taglib"
          xmlns="http://www.w3.org/1999/xhtml"
          version="2.0">
  <jsp:output doctype-root-element="html"
              doctype-public="-//W3C//DTD XHTML 1.0 Transitional//EN"
              doctype-system="http://www.w3c.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"/>
  <jsp:directive.page contentType="text/html"/>
  <html>
  <head>
    <title>Guess a number...</title>
    <link href="niceforms.css" rel="stylesheet" type="text/css" />
    <script language="javascript" type="text/javascript" src="niceforms.js"><!--  --></script>
  </head>
  <body>
    <h1>Guess a number...</h1>
    <f:view>
         <h:form id="NumberGuessMain" styleClass="niceform">
          
           <div>
           <h:messages id="messages" globalOnly="true"/>
           <h:outputText id="Higher"
                          value="Higher!"
                         rendered="#{numberGuess.randomNumber gt numberGuess.currentGuess}"/>
           <h:outputText id="Lower"
                          value="Lower!"
                         rendered="#{numberGuess.randomNumber lt numberGuess.currentGuess}"/>
              </div>
             
              <div>
        I'm thinking of a number between <h:outputText id="Smalles" value="#{numberGuess.smallest}"/> and
        <h:outputText id="Biggest" value="#{numberGuess.biggest}"/>. You have
        <h:outputText id="RemainingGuesses" value="#{numberGuess.remainingGuesses}"/> guesses.
        </div>
       
        <div>
        Your guess:
        <h:inputText id="inputGuess" value="#{numberGuess.currentGuess}" required="true" size="3"
                 rendered="#{(numberGuess.biggest-numberGuess.smallest) gt 20}">
          <f:validateLongRange maximum="#{numberGuess.biggest}"
                               minimum="#{numberGuess.smallest}"/>
        </h:inputText>
        <h:selectOneMenu id="selectGuessMenu" value="#{numberGuess.currentGuess}" required="true"
                       rendered="#{(numberGuess.biggest-numberGuess.smallest) le 20 and (numberGuess.biggest-numberGuess.smallest) gt 4}">
          <s:selectItems id="PossibilitiesMenuItems" value="#{numberGuess.possibilities}" var="i" label="#{i}"/>
        </h:selectOneMenu>
        <h:selectOneRadio id="selectGuessRadio" value="#{numberGuess.currentGuess}" required="true"
                       rendered="#{(numberGuess.biggest-numberGuess.smallest) le 4}">
          <s:selectItems id="PossibilitiesRadioItems" value="#{numberGuess.possibilities}" var="i" label="#{i}"/>
        </h:selectOneRadio>
              <h:commandButton id="GuessButton" value="Guess" action="guess"/>
        <s:button id="CheatButton" value="Cheat" action="cheat"/>
        <s:button id="GiveUpButton" value="Give up" action="giveup"/>
              </div>
             
              <div>
        <h:message id="message" for="inputGuess" style="color: red"/>
        </div>
       
         </h:form>
    </f:view>
  </body>
  </html>
</jsp:root>

注意,命令按钮怎样指定guess转换代替直接调用一个动作。
 
win.jspx 页面是可预言的:
Example 1.20.
<?xml version="1.0"?>
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page"
          xmlns:h="http://java.sun.com/jsf/html"
          xmlns:f="http://java.sun.com/jsf/core"
          xmlns="http://www.w3.org/1999/xhtml"
          version="2.0">
  <jsp:output doctype-root-element="html"
              doctype-public="-//W3C//DTD XHTML 1.0 Transitional//EN"
              doctype-system="http://www.w3c.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"/>
  <jsp:directive.page contentType="text/html"/>
  <html>
  <head>
    <title>You won!</title>
    <link href="niceforms.css" rel="stylesheet" type="text/css" />
  </head>
  <body>
    <h1>You won!</h1>
    <f:view>
      Yes, the answer was <h:outputText id="CurrentGuess" value="#{numberGuess.currentGuess}" />.
      It took you <h:outputText id="GuessCount" value="#{numberGuess.guessCount}" /> guesses.
      <h:outputText id="CheatedMessage" value="But you cheated, so it doesn't count!" rendered="#{numberGuess.cheat}"/>
      Would you like to <a href="numberGuess.seam">play again</a>?
    </f:view>
  </body>
  </html>
</jsp:root>
 
lose.jspx是相似的(我不会讨厌拷贝/粘贴的)。最后是JavaBean Seam组件:
 
Example 1.21.   NumberGuess.java
@Name("numberGuess")
@Scope(ScopeType.CONVERSATION)
public class NumberGuess implements Serializable {
  
   private int randomNumber;
   private Integer currentGuess;
   private int biggest;
   private int smallest;
   private int guessCount;
   private int maxGuesses;
   private boolean cheated;
  
   @Create        //       ⑴
   public void begin()
   {
      randomNumber = new Random().nextInt(100) + 1;
      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;
   }
 
   public void cheated()
   {
      cheated = true;
   }
  
   public boolean isCheat() {
      return cheated;
   }
  
   public List<Integer> getPossibilities()
   {
      List<Integer> result = new ArrayList<Integer>();
      for(int i=smallest; i<=biggest; i++) result.add(i);
      return result;
   }
  
}
 
⑴JSP页面第一次请求numberGuess组件,Seam将为它创建一个新的,并且@Create方法将被调用,允许组件自身初始化。
 
pages.xml文件启动一个Seam对话(后面会更多谈及),并且指定页面流定义使用对话页面流。
<?xml version="1.0" encoding="UTF-8"?>
<pages xmlns="http://jboss.com/products/seam/pages"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://jboss.com/products/seam/pages http://jboss.com/products/
seam/pages-2.1.xsd">
  <page view-id="/numberGuess.jspx">
    <begin-conversation join="true" pageflow="numberGuess"/>
  </page>
  <page view-id="/confirm.jspx">
    <begin-conversation nested="true" pageflow="cheat"/>
  </page>
</pages>
 
正如你所看见,这个Seam组件是纯粹的业务逻辑。它不需要知道任何有关用户的交互流。这潜在地使组件有更多的可重复利用。
   

1.5.2. 它怎样工作
TODO


1.6.一个完整的Seam应用程序: Hotel Booking例子
1.6.1. 介绍
booking应用程序是一个完整的旅馆房间预约系统,混合了下面特色:
*用户注册
*注册
*退出
*设置密码
*旅馆搜索
*旅馆选取
*房间预约
*预约确认
*现在预约列表
Booking应用程序使用JSF、 EJB 3.0 、 Seam和Facelets视图。也有一部分应用程序使用了Facelets, Seam, JavaBeans 和 Hibernate3。
 如果你玩这个应用程序很长时间,你会注意到的事情之一是:它是极其健壮的。你能玩返回按钮、游览器刷新、打开多窗口、输入你希望的差不多是无意义的数据,你将发现要使这个应用程序崩溃是很困难的事。你可能认为我们有这样的成就花了很多周来测试和修补程序缺陷。实事上,并不是这样的情况。Seam已被设计成专为构建健壮的网页应用程序,它使得这变得十分简单,并且以前你可能需要手动编码的很多健壮性的地方,Seam会自然地、自动地产生。
 当你浏览这个例子应用程序的的源代码,了解这个应用程序怎样工作,观察如何声明状态管理和集成验证时,已经习惯于完成这种健壮性。(?)
1.6.2. 总览booking 例子
这个项目的结构与最先的一个是同样的,安装和部署这个应用程序,请参考章节1.1“试例子”。一旦你已成功启动这个应用程序,你能通过指定浏览器的地址:http://localhost:8080/seam-booking/来访问这个应用程序。
 实现这个应用程序只有九个类(加上六上会话beans本地接口)。六个会话bean动作接收器包含实现列表的特色的所有业务逻辑。
* BookingListAction取回当前注册用户的现有的预约。
* ChangePasswordAction 更新当前注册用户的密码。
* HotelBookingAction  实现应用程序的核心功能:旅馆搜索、旅馆选取、房间预约和预约确认。这个功能作为一个“对话”被实现, 所以这是在这个应用程序中最令人感兴趣的类。
* RegisterAction 注册一个新的系统用户。

三个实体beans实现了这个应用程序的持久化领域模型。
* Hotel 代表一个旅馆的实体bean。
* Booking 代表一个现有的预约的实体bean。
* User 代表能进行旅馆预约的一个实体bean。
 
1.6.3. 理解 Seam“对话”
 我们鼓励你愉快地浏览源代码。在这个教程我们将专心在一个特殊的功能块:旅馆搜索、旅馆选取、房间预约和预约确认。从一个用户的视角来看,从选择一个旅馆到确认一个预约的每一件事情是一个连续的工作单元,一次“对话”。然而,搜索不是“对话”的一部分。用户能从同一个搜索结果页面,用不同的游览器标签选取多个旅馆。
 多数网页应用程序体系结构没有首先类构成物来代表一个“对话”。管理与“对话”关联的状态这会引起巨大的问题。通常,Java网页应用程序用一个组合物,它由两种技术构成:首先,一些状态被投入到HttpSession;其次,在每一次请求后,可持久化状态被刷新到数据库,并且每一次新的请求开始,从数据库重构它们。
 因为数据库是最少的可升级的层,这常常导致难以接收的可升级需求。增加延迟也是一个问题,由于每一次请求在数据库之间来去的额外的通信。为了减少这些额外的通信,Java应用程序常常引入一个数据缓存,在请求之间保存常用的已访问数据。这个缓存是必要的、低效的,因为失效基于一个LRU(最近最少使用算法)策略,而不是基于什么时候用户用数据完成了工作。此外,因为在多个并发事务之间缓存是共享的,我们引入了一个整体的问题的联合“筏”,维持缓存状态与数据库一致。
现在考虑保持在HttpSession的状态。通过非常小心的编程,我们可能会控制会话数据的大小。这比它所宣称的问题要多很多,因为网页浏览器允许特别的非线性导航。但是假设我们突然发现一个系统要求说一个用户被允许有多个并发“对话”,系统开发到了一半(这发生在我身上)。隔立会话状态的开发机制关联到不同的并发“对话”,并且合并了故障保护,当一个用户关闭了浏览器窗口或因为昏头昏脑点了“不”标签(到目前为止,我搞了这种事两次,一次是对客户端应用程序,一次是对Seam, 除了我是有名的精神病外 ??)中断了会话中的一个的时侯,确保“对话”状态被摧毁。
 
现在有一个更好的办法。
 
Seam引入“对话”上下文作为一个首先类构成物。你能安全保持对话状态在这个上下文中,并且确定它有一个定义明确的生命周期。甚至更好,你不必在应用程序服务器和数据库这间连续地来回“推”数据,因为对话上下文是一个天然的数据缓存,用户当前工作使用的数据的缓存。
 
通常,我们维持在对话上下文中的组件是有状态会话beans。(我们也能维持实体beans JavaBeans 在对话上下文中)。在java社区有一个古老的误传,有状态会话beans是伸缩性的一个杀手。在1998年,当 WebFoobar 1.0发布时这也许是真的。今天这不再是真的了。象JBoss AS这些应用程序服务器,对有状态会话bean状态的回响有极其先进的机制。(例如,JBoss EJB3容器执行细致回响,仅在那些bean属性值确实发生改变时回响)。注意,所有传统的技术层面的争论——为什么有状态beans是低效的,应公平地应用于HttpSession,这样看来,对难以捉摸的状态的实践,从业务层有状态会话bean组件到网页会话,企图改善性能是难以置信地被误导。用状态会话beans写一个非伸缩应用程序是当然可能的,通过不正确地使用有状态beans或者对不正当的事情应用了它们。但是,并不意味着你绝不要使用有状态会话beans。总之,Seam指导你向一个安全使用的模式走去。欢迎到Seam 2005.
 
好了,现在中止我激昂的演说,回到这个教程。
 
Booking应用程序例子显示如何使用不同范围的有状态组件一起合作来实现复杂的行为。Booking应用程序主页允许用户搜索旅馆。搜索结果被保持在Seam会话范围。当用户导航到这些旅馆中的一个,一个对话开始,并且一个对话范围组件回调会话范围组件取得所选择的旅馆。
 
Booking也演示RichFaces Ajax的使用,而不用手写JavaScript来实现富客户端行为。
 
搜索功能用一个会话范围的有状态会话bean来实现,类似于上面我们在信息列表例子中看见的那个。
 Example 1.22.   HotelSearchingAction.java
 @Stateful      //         ⑴                                                                
@Name("hotelSearch")  
@Scope(ScopeType.SESSION)
@Restrict("#{identity.loggedIn}")  //  ⑵                                          
public class HotelSearchingAction implements HotelSearching
{
 
   @PersistenceContext
   private EntityManager em;
 
   private String searchString;
   private int pageSize = 10;
   private int page;
 
   @DataModel           //  ⑶                                                    
   private List<Hotel> hotels;
 
   public void find()
   {
      page = 0;
      queryHotels();
   }
   public void nextPage()
   {
      page++;
      queryHotels();
   }
    
   private void queryHotels()
   {
      hotels =
          em.createQuery("select h from Hotel h where lower(h.name) like #{pattern} " +
                         "or lower(h.city) like #{pattern} " +
                         "or lower(h.zip) like #{pattern} " +
                         "or lower(h.address) like #{pattern}")
            .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;
   }
 
   @Factory(value="pattern", scope=ScopeType.EVENT)
   public String getSearchPattern()
   {
      return searchString==null ?
            "%" : '%' + searchString.toLowerCase().replace('*', '%') + '%';
   }
  
   public String getSearchString()
   {
      return searchString;
   }
 
   public void setSearchString(String searchString)
   {
      this.searchString = searchString;
   }
                     //    ⑷                                                      
   @Remove
   public void destroy() {}
}
 
⑴EJB标准@Stateful注释指明这个类是一个有状态会话bean。有状态会话beans默认是被放到“对话”上下文中。
⑵@Restrict注释对组件应用了一个安全约束。它只准许注册用户访问组件。在安全章节会解释更多有关Seam安全性的问题。
⑶@DataModel注释暴露一个列表作为一个JSF ListDataModel。这对搜索屏实现可点击列表变得容易。在这个案例,旅馆列表用“对话”中的变量hotels暴露给页面作为一个ListDataModel。
⑷EJB标准@Remove注释指定那个有状态会话bean应被移去,并且在注释方法调用后它的状态被摧毁。当Seam摧毁会话上下文时,这个方法被调用。
 
应用程序的主页是一个Facelets页面。让我们看看与搜索旅馆有关的片段:
Example 1.23.   main.xhtml
<ui:define name="content">
 
<div class="section">
 
    <span class="errors">
       <h:messages id="messages" globalOnly="true"/>
    </span>
  
    <h1>Search Hotels</h1>
 
       <h:form id="searchCriteria">
       <fieldset>
          <h:inputText id="searchString" value="#{hotelSearch.searchString}" style="width: 165px;">
         <a:support id="onkeyup" event="onkeyup" actionListener="#{hotelSearch.find}" reRender="searchResults" />
       </h:inputText>
        
          <a:commandButton id="findHotels" value="Find Hotels" action="#{hotelSearch.find}" reRender="searchResults"/>
        
       <a:status id="status">
          <f:facet id="StartStatus" name="start">
             <h:graphicImage id="SpinnerGif" value="/img/spinner.gif"/>
          </f:facet>
       </a:status>
          <br/>
       <h:outputLabel id="MaximumResultsLabel" for="pageSize">Maximum results:</h:outputLabel> 
       <h:selectOneMenu id="pageSize" value="#{hotelSearch.pageSize}">
          <f:selectItem id="PageSize5" itemLabel="5" itemValue="5"/>
          <f:selectItem id="PageSize10" itemLabel="10" itemValue="10"/>
          <f:selectItem id="PageSize20" itemLabel="20" itemValue="20"/>
       </h:selectOneMenu>
    </fieldset>

    </h:form
页: [1]
查看完整版本: 第1章 seam 教程(2)