alartin's profileWindows Live 共享空间PhotosBlogListsMore Tools Help

Blog


    January 22

    JBoss Seam 页面流释疑(1)

    Seam的页面流pageflow是很容易让人迷惑的。

    这里首先要弄清楚术语,Web程序通常都是在不同页面间来回跳转来实现应用逻辑的,所以通常的说法是页面导航Page Navigation, 这是最普遍和广泛的说法。因此对于页面导航Page Navigation的约定,被称为页面导航规则Page Navigation Rule。这几个术语是独立于语言和具体Web框架的。

    例如,在JSF开发中,我们使用outcome来实现页面导航。outcome本身就是一个字符串,我们通过某个方法返回的outcome字符串来判断下一步的逻辑步骤。当然这个工作需要通过页面和outcome字符串的匹配设置来完成。例如在Seam的猜数字应用中,我们也可以使用JSF的outcome来实现页面导航:

    <navigation-rule>
        <from-view-id>/numberGuess.jsp</from-view-id>
            
        <navigation-case>
            <from-outcome>guess</from-outcome>
            <to-view-id>/numberGuess.jsp</to-view-id>
            <redirect/>
        </navigation-case>
    
        <navigation-case>
            <from-outcome>win</from-outcome>
            <to-view-id>/win.jsp</to-view-id>
            <redirect/>
        </navigation-case>
            
        <navigation-case>
            <from-outcome>lose</from-outcome>
            <to-view-id>/lose.jsp</to-view-id>
            <redirect/>
        </navigation-case>
    
    </navigation-rule>

    我们可以看到JSF页面导航配置的三要素:

    1. 从哪个地方来:from-view-id指明从numberGuess.jsp页面来
    2. 某个可能的产出(outcome)是什么:from-outcome指明这个产出是win
    3. 这个可能的产出对应到哪里去:to-view-id指明如果产出是win的话,那么导向win.jsp页面

    但是你可能立刻就能发现这种处理方式的弊病来:

    1. 这种方式需要在代码中处理导航逻辑。尽管我们配置页面导航比较容易,但是实际上,我们增加了代码中的复杂性(有可能得不偿失啊)。
    2. 自由度太大。开发人员可以随意设置outcome的值和其导向页面这样当页面导航复杂度增加的时候管理起来非常困难。
    3. 约束性太小。这个缺点和第二个缺点有一定联系,约束性太小意味着,开发人员往往需要查看所有的页面文件*.jsp和整个配置文件navigation.xml才能明白全部的页面导航规则

    这种方式的页面导航被称为无状态的页面导航。

    需要强调的是:对于这种无状态的页面导航方式,除了JSF导航(使用navigation.xml)外,Seam还支持一种自己实现的无状态页面导航(使用pages.xml或者某个页面的xxx.page.xml):

    <page view-id="/admin/admin.xhtml">
            <navigation>
                <rule if-outcome="ship">
                    <redirect view-id="/admin/ship.xhtml"/>
                </rule>
                <rule if-outcome="accept">
                    <redirect view-id="/admin/accept.xhtml"/>
                </rule>
            </navigation>
        </page>

    这里Seam也使用了类似JSF的页面导航三要素:

    1. 从哪里来:page元素的view-id属性指明从/admin/admin.xhtml页面来
    2. 某个可能的产出(outcome): navigation元素中嵌套的rule元素的if-outcome属性指明可能的一个outcome是ship
    3. 到哪里去:rule元素中嵌套的redirect元素的view-id属性指明如果outcome属性是ship的话,导向ship.xhtml页面

    如果,在你的Seam应用程序中你准备使用无状态的页面导航的话,你有两种选择:

    1. 使用JSF导航:配置navigation.xml
    2. 使用Seam导航: 配置pages.xml或者某个xxx.page.xml

    我们将在后面的文章中比较这两种选择。

    Seam不仅仅只支持这种无状态的页面导航!更棒的是,Seam支持有状态的页面导航

    提醒:在Seam中,Seam实现的页面导航被成为页面流Pageflow

    1. JSF页面导航被称为页面导航,而页面导航是广泛的术语
    2. Seam实现的页面导航被成为页面流Pageflow
    3. 页面流可以是无状态的页面导航,Seam自己实现的无状态的页面导航
    4. 页面流也可以是有状态的页面导航,Seam通过JBPM实现的有状态的页面导航

    在我们讲解有状态的页面导航之前,我们先回顾一下无状态页面导航的弊病包括:虽然页面配置简单,但是代码中要控制页面导航规则;无法很容易的看清楚导航规则,因为约束小。

    Seam通过使用JBPM来帮助我们支持创建有状态的页面导航

    需要注意的是:Seam在两种层次上使用JBPM:

    1. 一种是在页面导航中使用JBPM;
    2. 另一种是在业务流程管理中使用JBPM.

    这两者的层次是不一样的,这也是容易导致Seam初学者迷惑的地方,在这里我们先弄清楚这一点:JBPM是独立的流程管理引擎,在Seam中它被使用在两个不同的地方:页面导航和流程管理中。

    实际上,Seam开发人员利用JBPM强大的定制功能,专门为页面导航定制了流程管理。Seam的使用者应该记住在Seam有状态的页面导航中page元素是JBPM节点Node的一种!这从侧面证明了JBPM强大的功能!

    OK, 我们接着说JBPM在Seam中的使用。

    首先看一下JBPM在页面导航中的作用:用来实现有状态的页面导航。

    JBPM在这里用来定义单个对话中的页面流。一个Seam对话指的是单个用户相对短时期内的交互

    我们再来看一下JBPM在流程管理中的作用:用来进行流程管理。

    业务流程可以跨越多个用户,多个对话。其流程被持久化到JBPM数据库中,所以它是长时期的。当然,管理多用户活动当然比管理单个用户活动复杂的多,包括复杂的任务管理和多路径并发管理。

    你可能在你的应用程序中同时在这两个地方都使用JBPM!

    OK,让我们回到有状态页面导航的主题上来。

    还是使用猜数字应用。这回我们需要用xxx.jpdl.xml文件来配置有状态的页面导航(pageflow.jpdl.xml):

    <pageflow-definition name="numberGuess">
        
       <start-page name="displayGuess" view-id="/numberGuess.jsp">
          <redirect/>
          <transition name="guess" to="evaluateGuess">
          	<action expression="#{numberGuess.guess}" />
          </transition>
       </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>
       <process-state name="cheat">
          <sub-process name="cheat"/>
          <transition name="displayGuess"/>
       </process-state>
    
       <page name="win" view-id="/win.jsp">
          <redirect/>
          <end-conversation />
       </page>
       
       <page name="lose" view-id="/lose.jsp">
          <redirect/>
          <end-conversation />
       </page>
       
    </pageflow-definition>
    我们可以看到有状态的页面流是通过JBPM的JPDL(xxx.jpdl.xml)来配置的,几个要素:
    1. 页面流定义: pageflow-definition元素指明页面流名字
    2. 起始点:起始点包括两种可能的元素:start-page元素或者start-state元素(区别我们稍后介绍)指明页面流的起始点
    3. 页面:page元素指明页面流中的各个页面
    4. 转移:transition元素代替了无状态页面流的产出outcome作用,这里的转移将在具体页面中使用(例如在guessNumber.jspx文件中):<h:commandButton value="Guess" action="guess"/>,这个按钮激发的动作action名为guess, 而这个guess就是xxx.jpdl.xml文件中配置的一个转移(注意和无状态的页面流区别,这里的动作action指的是转移,而不是传统意义上的动作或方法调用)
    5. 动作:在转移元素中的action元素指明这个转移中发生的事件(和JBPM的转移的概念一致),还是上面的例子:<action expression="#{numberGuess.guess}" />,在xxx.jpdl.xml文件中转移guess中有个动作,这个动作是调用numberGuess对象的guess方法,并且这个转移guess将导向evaluateGuess这个决策。所以逻辑是这样的: 用户 -> 点击Guess按钮 ->Guess按钮的action激发guess转移 ->guess转移激发numberGuess对象的guess方法 -> guess转移导向evaluateGuess决策
    6. 决策:decision元素
    7. 流程状态:process-state元素包含子流程元素sub-process

    January 10

    JBoss Seam: Seam应用框架

    Seam Application Framework, 听起来挺吓人的,也很容易让人犯糊涂,Seam本身不就是个Web框架么,那里来的应用框架. 其实称之为Seam Component Template Framework更加合适. Seam 组件模板框架.

    原因很简单, 这个名词看起来吓人,其实就干一件事情, 把Seam组件开发再简化(尽管通过注释等方法, 开发Seam组件非常简单了). 怎么再简化呢? 大家可以想一想Struts的Dynamic Action历史就明白了. 也就是通过配置来创建基本的Seam组件, 你可以不用写类了! 在componets.xml文件中配置.例如下面的配置信息能够创建一个基本的实现CRUD功能的Seam组件:

    <framework:entity-home name="personHome" 
                           entity-class="eg.Person" 
                           entity-manager="#{personDatabase}">
        <framework:id>#{param.personId}</framework:id>
    </framework:entity-home>
    如果你讨厌通过XML配置来替代编程的话(反正我讨厌), 你也可以通过类继承来实现:
    @Stateful
    @Name("personHome")
    public class PersonHome extends EntityHome<Person> implements LocalPersonHome {
        @RequestParameter String personId;
        @In EntityManager personDatabase;
        
        public Object getId() { return personId; }
        public EntityManager getEntityManager() { return personDatabase; }
        
    }

    一头雾水吧?让我们看看Seam的代码.所有的Application Framework代码位于org.jboss.seam.framework下面.

    最重要的两个类为Home类和Query类.

    Home类: 提供了实体类的持久化操作CRUD. Seam提供两种实现:EntityHome和HibernateEntityHome. 一个是给JPA的,一个是给Hibernate的.

    Query类: 顾名思义,提供实体类查询的.同样,Seam提供两种实现: EntityQuery, HibernateEntityQuery.一个是给JPA的,一个是给Hibernate的.

    Home类和Query类天生就能和会话范围,事件范围和对话范围一起工作.

    需要注意的是:Seam Application Framework只能和Seam管理的持久化上下文一起工作,Seam-managed Persistence Context.

    默认的情况下,Application Framework的组件查找一个名为entityManager的持久化上下文.

    来点仔细的. 我们看一下Home, 其实Home就是给某个实体类干CRUD活的,类似于EJB3的Session Bean, 比如说我们有个实体类Person, 那么PersonHome就是创建,修改,删除Person的工具么.

    @Entity
    public class Person { // 这个是我们的实体类
        @Id private Long id;
        private String firstName;
        private String lastName;
        private Country nationality;
        
        //getters and setters...
    }
    1. 可以通过在components.xml配置相对应的Home类:
    <framework:entity-home name="personHome" entity-class="eg.Person" />
    2. 或者自己写类来继承
    @Name("personHome")
    public class PersonHome extends EntityHome<Person> {} // 参数是实体类啊

    反正我觉的第二种方法最合适,如果你是个程序员的话.

    然后你就自动获得PersonHome类了, 内置persist(), remove(), update()和getInstance()方法. 酷吧!当然,你要调用remove()或update()方法前,要先设置setId(Object id)方法.

    OK, 那么怎么使用呢, 我们看一个例子, 我们可以直接在页面上使用Home类:

    <h1>Create Person</h1>
    <h:form>
        <div>First name: <h:inputText value="#{personHome.instance.firstName}"/></div>
        <div>Last name: <h:inputText value="#{personHome.instance.lastName}"/></div>
        <div>
            <h:commandButton value="Create Person" action="#{personHome.persist}"/>
        </div>
    </h:form>

    用户在页面上的输入直接就能创建一个Person实体对象了,当然,通过PersonHome创建的.不过我们会觉得使用personHome.instance很不爽, 其实我们更加愿意使用person来代替它. 做到这一点也很容易:

    1. 在components.xml中配置

    <factory name="person" 
             value="#{personHome.instance}"/>
    
    <framework:entity-home name="personHome" 
                           entity-class="eg.Person" />
    2.在自己写的类中加一个方法,然后用@Factory注释
    @Name("personHome")
    public class PersonHome extends EntityHome<Person> {
        // 很简单,这个方法仅仅调用的getInstance()而已
        @Factory("person")
        public Person initPerson() { return getInstance(); }
        
    }
    OK, 现在我们的页面就可以变成:
    <h1>Create Person</h1>
    <h:form>
        <div>First name: <h:inputText value="#{person.firstName}"/></div>
        <div>Last name: <h:inputText value="#{person.lastName}"/></div>
        <div>                                              // 这里没变
            <h:commandButton value="Create Person" action="#{personHome.persist}"/>
        </div>
    </h:form>
    相信你已经很满意了吧.那么,也许你想搞个页面来编辑某个已经持久化的Person实体,那么你需要在pages.xml中配置页面参数:
    <pages>
        <page view-id="/editPerson.jsp"> // 编辑页面需要person id参数
            <param name="personId" value="#{personHome.id}"/>
        </page>
    </pages>
    让我们看看编辑页面:
    <h1> // 如果personHome还没有管理某个person实体的话, 页面显示为 创建Person, 否则显示 编辑Person
         // 此时,页面参数personId 为空
        <h:outputText rendered="#{!personHome.managed}" value="Create Person"/>
        <h:outputText rendered="#{personHome.managed}" value="Edit Person"/>
    </h1>
    <h:form>
        <div>First name: <h:inputText value="#{person.firstName}"/></div>
        <div>Last name: <h:inputText value="#{person.lastName}"/></div>
        <div>
            //如果personHome还没有管理某个person实体的话,按钮显示创建,否则的话显示两个按钮 更新和删除
            // 此时,页面参数personId 为空
            <h:commandButton value="Create Person" action="#{personHome.persist}" rendered="#{!personHome.managed}"/>
            <h:commandButton value="Update Person" action="#{personHome.update}" rendered="#{personHome.managed}"/>
            <h:commandButton value="Delete Person" action="#{personHome.remove}" rendered="#{personHome.managed}"/>
        </div>
    </h:form>
    关于Home类,我们先讲这么多,再来看看Query类.
    Query类通常用来查询一个实体对象的列表.比如获得所有Person:
    1.通过components.xml配置
    <framework:entity-query name="people" 
                            ejbql="select p from Person p"/>
    使用页面:
    <h1>List of people</h1>
    <h:dataTable value="#{people.resultList}" var="person">
        <h:column>
            <s:link view="/editPerson.jsp" value="#{person.firstName} #{person.lastName}">
                <f:param name="personId" value="#{person.id}"/>
            </s:link>
        </h:column>
    </h:dataTable>
    要是我们想分页看呢:
    <framework:entity-query name="people" 
                            ejbql="select p from Person p" 
                            order="lastName"  // 排序
                            max-results="20"/> // 分页最大结果
    在pages.xml配置:
    <pages>
        <page view-id="/searchPerson.jsp">
            <param name="firstResult" value="#{people.firstResult}"/>
        </page>
    </pages>
    更改后的页面:
    <h1>Search for people</h1>
    <h:dataTable value="#{people.resultList}" var="person">
        <h:column>
            <s:link view="/editPerson.jsp" value="#{person.firstName} #{person.lastName}">
                <f:param name="personId" value="#{person.id}"/>
            </s:link>
        </h:column>
    </h:dataTable>
    
    <s:link view="/search.xhtml" rendered="#{people.previousExists}" value="First Page">
        <f:param name="firstResult" value="0"/> // <<
    </s:link>
    
    <s:link view="/search.xhtml" rendered="#{people.previousExists}" value="Previous Page">
        <f:param name="firstResult" value="#{people.previousFirstResult}"/>
    </s:link> 
    <s:link view="/search.xhtml" rendered="#{people.nextExists}" value="Next Page">
        <f:param name="firstResult" value="#{people.nextFirstResult}"/>
    </s:link> 
    <s:link view="/search.xhtml" rendered="#{people.nextExists}" value="Last Page">
        <f:param name="firstResult" value="#{people.lastFirstResult}"/>
    </s:link>
    我们甚至可以监听一些事件,例如更新,删除等等然后根据这些事件,刷新查询:
    <event type="org.jboss.seam.afterTransactionSuccess">
        <action execute="#{people.refresh}" />
    </event>
    甚至可以限定是Person实体的事件:
    <event type="org.jboss.seam.afterTransactionSuccess.Person">
        <action execute="#{people.refresh}" />
    </event>

    需要注意的是:上面的Query例子都用的是配置形式,你也可以写自己的类来继承.Seam通常将Query的继承类命名为XXXList, 将Home类命名为XXXHome, 其中XXX是实体类.

    有趣的Controller类其实就是一些实体类的帮助类,让你更加容易使用内置组件和内置组件的方法.

            like   session bean                          |                           like session bean

    XXXList/Query(负责XXX的查询)  <----  你的实体类XXX  ----> XXXHome (负责XXX的CRUD工作)

                                                                |

                                                       Controller(XXX的帮助类)

    December 18

    JBoss Seam事件机制(3):页面动作

    在JBoss Seam事件机制(1)概述中讲到,Seam中的页面动作发生在页面渲染之前,我们在WEB-INF/pages.xml文件中配置页面动作。我们还提到了page元素中的view-id不一定非要是JSP或者Facelet页面,这给整合其他WEB框架留了空间,并且能够让我们处理非JSF的请求。另外页面动作可以返回一个JSF输出,通过JSF输出来定制导向。除此之外,在page元素中,我们可以使用多个action元素来完成有条件的页面动作:

    <pages>
        <page view-id="/hello.jsp">
            <action execute="#{helloWorld.sayHello}" if="#{not validation.failed}"/>
            <action execute="#{hitCount.increment}"/>
        </page>
    </pages>
    页面参数
    我们知道一个JSF请求(表单提交)包括(封装)了动作和参数,所以一个页面动作也许需要参数。使用GET的请求可以做书签,因为
    页面参数都作为可读的请求参数处理了。但是JSF表单使用的是POST。Seam能够让我们将请求参数和模型属性相关联和映射:
    <pages>
          <page view-id="/hello.jsp" action="#{helloWorld.sayHello}">
              <param name="firstName" value="#{person.firstName}"/>
              <param name="lastName" value="#{person.lastName}"/>
          </page>
      </pages>
    page元素中的param参数是双向的,就像JSF输入的值绑定一样。
    • 当一个非Faces请求(GET)请求某个视图view-id的时候,Seam经过正确的类型转换后将请求的参数值赋给模型对象.

    • 任何<s:link> 或者 <s:button> 都显式得包含请求参数。参数值由渲染阶段(<s:link>渲染)的值绑定计算结果来决定.

    • 请求某个视图view-id的任何带有<redirect/>的导向规则都显式得包含请求参数 . 参数值由调用应用阶段结束时的值绑定计算来决定.

    • 值通过任何对给定视图view-id的页面的JSF表单提交来传递. 这意味着视图参数有些类似于Faces请求中页面范围的上下文变量。

    关键的思想是我们如何从另一个页面到/hello.jsp(或者从/hello.jsp又回到/hello.jsp), 在值绑定中的模型的值仍然被记住,而不需要任何的对话或者其他的服务器端的状态。

    请求值的传递

    如果只是name属性被指定的话,请求参数使用PAGE页面上下文(没有和模型属性映射)来传递。

    <pages>
          <page view-id="/hello.jsp" action="#{helloWorld.sayHello}">
              <param name="firstName" /> // 没有value
              <param name="lastName" />
          </page>
    </pages>
    如果你想创建多层次主从模式的CRUD页面时,页面参数的传递就有必要了。你可以通过页面参数的传递来记住先前你在哪个视图上
    (例如你在保存页面上,必须记住上个页面你正在编辑的实体)
    • 如果参数以视图的页面参数列出的情况下,任何<s:link> 或者 <s:button> 显式的传递请求参数.

    • 值通过任何对给定视图view-id的页面的JSF表单提交来传递. 这意味着视图参数有些类似于Faces请求中页面范围的上下文变量。

    听起来太复杂了,这有必要么?当你用它的时候你就知道了。这值得下功夫去理解。页面参数是在非Faces请求间传递状态的最佳方式。如果你想让你的搜索结果页面也能做书签的话,我们就能够在同一个代码中使用它来处理POST和GET两种请求了。页面参数能够减少视图定义中请求参数列表的重复性,并且让在代码中处理重定向更加简单。

    对话和验证

    导向

    在Seam中,你可以使用JSF的faces-config.xml文件来设置标准的JSF向导规则。但是JSF的向导规则有很多缺点:

    • 在重定向的时候无法指定使用的请求参数.

    • 无法根据规则来启动或者结束对话.

    • 规则是根据动作方法返回的值来工作的,无法通过EL表达式来计算.

    还有一个问题就是如果在Seam中使用标准的JSF导向,业务逻辑将散落在pages.xml和faces-config.xml两个配置文件中,很散乱。因此建议只是用pages.xml来配置页面导向

    我们看一下如何转换:

    <navigation-rule> // JSF标准导向,在faces-config.xml中配置
        <from-view-id>/editDocument.xhtml</from-view-id>
        
        <navigation-case>
            <from-action>#{documentEditor.update}</from-action>
            <from-outcome>success</from-outcome>
            <to-view-id>/viewDocument.xhtml</to-view-id>
            <redirect/>
        </navigation-case>
        
    </navigation-rule>
    <page view-id="/editDocument.xhtml"> // 等价的Seam导向,在pages.xml中配置
        
        <navigation from-action="#{documentEditor.update}">
            <rule if-outcome="success">
                <redirect view-id="/viewDocument.xhtml"/>
            </rule>
        </navigation>
        
    </page>
    <page view-id="/editDocument.xhtml"> // 改良版1, 甚至不使用JSF返回的string
        
        <navigation from-action="#{documentEditor.update}" 
                       evaluate="#{documentEditor.errors.size}">
            <rule if-outcome="0"> // 如果错误数量为0,就代表OK
                <redirect view-id="/viewDocument.xhtml"/>
            </rule>
        </navigation>
        
    </page>
    <page view-id="/editDocument.xhtml"> // 改良版2,判断错误是否为空
        
        <navigation from-action="#{documentEditor.update}">
            <rule if="#{documentEditor.errors.empty}"> 
                <redirect view-id="/viewDocument.xhtml"/>
            </rule>
        </navigation>
    </page>
    <page view-id="/editDocument.xhtml"> // 如果你希望编辑后,结束对话
        
        <navigation from-action="#{documentEditor.update}">
            <rule if="#{documentEditor.errors.empty}">
                <end-conversation/>
                <redirect view-id="/viewDocument.xhtml"/>
            </rule>
        </navigation>
        
    </page>
    <page view-id="/editDocument.xhtml"> // 改良版3,或者你需要将参数传递下去
        // 如果我们结束了对话,后续的请求无法知道哪个document我们感兴趣,我们需要将document id作为参数传递
        <navigation from-action="#{documentEditor.update}">
            <rule if="#{documentEditor.errors.empty}">
                <end-conversation/>
                <redirect view-id="/viewDocument.xhtml">
                    <param name="documentId" value="#{documentEditor.documentId}"/>
                </redirect>
            </rule>
        </navigation>
        
    </page>
    值得注意的是:JSF对于输出返回为Null的采用特殊处理,如果返回为Null的话指向原页面(重新显示页面)。但在Seam中不太一样。
    <page view-id="/editDocument.xhtml">
        
        <navigation from-action="#{documentEditor.update}">
            <rule> // 这里的规则对非Null输出是有效的,但是对Null输出无效
                <render view-id="/viewDocument.xhtml"/>
            </rule>
        </navigation>
        
    </page>
    需要变成:
    <page view-id="/editDocument.xhtml">
        // 去掉rule元素
        <navigation from-action="#{documentEditor.update}">
            <render view-id="/viewDocument.xhtml"/>
        </navigation>
        
    </page>
    <page view-id="/editDocument.xhtml">
        // view-id也可以使用EL表达式指定
        <navigation if-outcome="success">
            <redirect view-id="/#{userAgent}/displayDocument.xhtml"/>
        </navigation>
        
    </page>
    细粒度的定义导向,页面动作和参数
    如果你有很多页面动作和参数,或者有很多导向规则,你可能想使用多个文件来配置。这时候你可以使用view-id来创建响应的page.xml
    文件。例如你有个view-id为/calc/calculator.jsp的页面,你可以创建calc/calculator.page.xml文件来配置,内容如下:
     
    <page action="#{calculator.calculate}"> // 使用page元素作为根元素
        <param name="x" value="#{calculator.lhs}"/> // 指定参数
        <param name="y" value="#{calculator.rhs}"/>
        <param name="op" converter="#{operatorConverter}" value="#{calculator.op}"/>
    </page>

    JBoss Seam的事件机制(2):内置的上下文事件

    在JBoss Seam的事件机制(1)概述中我们提到Seam提供了内置的上下文事件,我们可以为每个事件定制自己的监听器:

    关于验证的事件

    • org.jboss.seam.validationFailed, 当JSF验证失败的时候被调用

    关于变量的事件

    • org.jboss.seam.preSetVariable.<name>
    • org.jboss.seam.postSetVariable.<name>
    • org.jboss.seam.preRemoveVariable.<name>
    • org.jboss.seam.postRemoveVariable.<name>

    关于上下文的事件

    • org.jboss.seam.preDestroyContext.<SCOPE>
    • org.jboss.seam.postDestroyContext.<SCOPE>

    关于对话的事件

    • org.jboss.seam.beginConversation
    • org.jboss.seam.noConversation,当需要长时间运行的对话,缺没有的时候被调用
    • org.jboss.seam.endConversation

    关于页面流的事件

    • org.jboss.seam.beginPageflow.<name>
    • org.jboss.seam.endPageflow.<name>

    关于流程和任务的事件

    • org.jboss.seam.createProcess.<name>
    • org.jboss.seam.endProcess.<name>
    • org.jboss.seam.initProcess.<name>
    • org.jboss.seam.startTask.<name>
    • org.jboss.seam.endTask.<name>
    • org.jboss.seam.initTask.<name>

    关于组件的事件

    • org.jboss.seam.postCreate.<name>
    • org.jboss.seam.preDestroy.<name>

    关于JSF生命周期的事件

    • org.jboss.seam.beforePhase
    • org.jboss.seam.afterPhase
    • org.jboss.seam.postInitialization

    关于认证和安全的事件

    • org.jboss.seam.preAuthenticate.<name>
    • org.jboss.seam.postAuthenticate.<name>
    • org.jboss.seam.notLoggedIn
    • org.jboss.seam.remeberMe 如果Seam在客户端Cookie中发现username时发生

    关于例外的事件

    • org.jboss.seam.exceptionHandled.<type>
    • org.jboss.seam.exceptionHandled
    • org.jboss.seam.exceptionNotHandled

    关于事务的事件

    • org.jboss.seam.afterTransactionSucess.<name>
    • org.jboss.seam.afterTransactionSucess

    JBoss Seam的事件机制(1):概述

    JBoss Seam如何做到松耦合的架构呢?光有具备上下文的组件模型是不够的。还必须有:

    1. 事件模型。事件模型通过事件<-->监听器模式来构建。采用的方式是象JSF方法绑定一样的方式实现。
    2. 使用注释和拦截器来纵向切入实现业务逻辑的组件,从而达到松耦合的事件触发和响应

    JBoss Seam的组件模型本身就是为事件驱动的应用来设计的。这些事件都是通过JSF的表达式语言的方法绑定来映射的。在JBoss Seam中,事件可以分为:

    1. JSF事件,例如:
    2. <h:commandButton value="Click me!" action="#{helloWorld.sayHello}"/> JSF的按钮绑定动作
    3. jBPM状态转移事件,例如:
    4. <start-page name="hello" view-id="/hello.jsp">
          <transition to="hello">
              <action expression="#{helloWorld.sayHello}"/>
          </transition>
      </start-page> jBPM流程或页面流定义
    5. Seam页面动作,页面事件发生在我们渲染一个页面之前,我们通过在WEB-INF/pages.xml文件中配置页面动作。我们可以为一个特定的JSF视图ID (View ID)设定一个页面动作,例如:
    6. <pages>
          <page view-id="/hello/hello.jsp" action="#{helloWorld.sayHello1}"/>
      </pages>
      我们也可以使用通配符为一个页面模式设定页面动作,例如:
      <pages>
          <page view-id="/hello/*" action="#{helloWorld.sayHello2}"/>
      </pages>
      如果多个页面动作匹配当前的页面视图的话,那么所有动作按照最窄到最宽泛的顺序依次调用
      例如上述sayHello1,sayHello2动作都和hello.jsp匹配,那么在hello.jsp页面上先调用
      sayHello1然后调用sayHello2。页面动作可以返回一个JSF的输出outcome,如果outcome
      不为null的话,Seam将使用定义好的规则去导向到一个视图。更棒的是page元素中的视图
      view-id可以不是JSP页面或者Facelet页面,允许我们通过view-id来整合基于动作的Struts
      或者Webwork框架。你可以使用action元素指定多个条件页面动作(在某种条件下才执行的动作)
      例如:
      <pages>
          <page view-id="/hello.jsp">
              <action execute="#{helloWorld.sayHello}" if="#{not validation.failed}"/>
              <action execute="#{hitCount.increment}"/>
          </page>
      </pages>
       
    7. Seam组件驱动的事件,Seam组件之间可以直接通过方法调用来相互沟通。有状态的组件可以实现观察者模式。虽然如此,Seam提供的组件驱动的事件比直接通过方法调用更加松耦合。我们来看一下如何设置组件驱动的事件。我们可以在components.xml文件中设置事件监听器(观察者):
    8. <components>
          <event type="hello"> // 设置事件, Observable
              <action execute="#{helloListener.sayHelloBack}"/> // 设置监听器,Observer
              <action execute="#{logger.logHello}"/> // 设置监听器,Observer
          </event>
      </components> 你可能想问事件类型type="hello"是什么,它只是一个任意的字串。事件类型将在组件驱动事件的时候使用
      (raiseEvent)。当事件发生时,在事件中注册的动作将根据在components.xml文件中的次序依次调用。你可能要问:我如何
      在组件中触发一个事件呢?基本上,你有两种选择:使用内置的组件和通过注释。下列代码在运行HelloWord的sayHello方法时
      触发事件类型为"hello"的事件(在components.xml文件中配置):
      @Name("helloWorld")
      public class HelloWorld {
          public void sayHello() {
              FacesMessages.instance().add("Hello World!");
              Events.instance().raiseEvent("hello"); // Events 内置组件 触发hello事件
          }
      }
      @Name("helloWorld")
      public class HelloWorld {
          @RaiseEvent("hello") // 注释 触发hello事件
          public void sayHello() {
              FacesMessages.instance().add("Hello World!");
          }
      }
      我们看到HelloWorld的sayHello方法其实是事件的产生者。值得注意的是事件的产生者和事件的消费者
      (监听者)没有任何依赖性,例如下面的hello监听器和上述的HelloWord没有依赖:
      @Name("helloListener") // 在components.xml文件的hello事件中注册的监听者
      public class HelloListener {
          public void sayHelloBack() { // 当hello事件触发后,监听者调用该监听方法
              FacesMessages.instance().add("Hello to you too!");
          }
      }
      注意:如果你讨厌在components.xml文件配置太多事件和监听器,那么你也可以使用注释来配置:
      @Name("helloListener")
      public class HelloListener {
          @Observer("hello") // 指明使用sayHelloBack方法来监听hello事件
          public void sayHelloBack() {
              FacesMessages.instance().add("Hello to you too!");
          }
      }
      到这里你会发现我们根本没有用事件对象!实际上,事件产生者和事件消费者之间根本不必有事件对象来
      处理状态的问题。状态问题由Seam的上下文处理了,并且在Seam组件之间共享!
      不过,你要真的想要一个事件对象,可以象这样:
      @Name("helloWorld") // 事件产生者
      public class HelloWorld {
          private String name;
          public void sayHello() {
              FacesMessages.instance().add("Hello World, my name is #0.", name);
              Events.instance().raiseEvent("hello", name); // name是String参数,传递多个Object参数
          }
      }
      @Name("helloListener") // 事件监听者
      public class HelloListener {
          @Observer("hello")
          public void sayHelloBack(String name) {
              FacesMessages.instance().add("Hello #0!", name);
          }
      }
    9. Seam具有上下文的事件,Seam本身定义了一系列内置的事件。注意这些事件的名称都是字符串定义的:

              Seam组件可以监听(观察)这些事件,就像他们观察组件驱动的事件一样。我们将在别的文章中专门讲述。

    August 29

    JBoss Seam: 具有上下文的组件模型

    Chapter 3. The contextual component model
    Prev     Next

    Chapter 3. The contextual component model 具有上下文的组件模型

    The two core concepts in Seam are the notion of a context and the notion of a component. Components are stateful objects, usually EJBs, and an instance of a component is associated with a context, and given a name in that context. Bijection provides a mechanism for aliasing internal component names (instance variables) to contextual names, allowing component trees to be dynamically assembled, and reassembled by Seam.

    Seam中两个核心概念是上下文概念和组件概念。组件是有状态的对象,通常是EJB,一个组件的实例和一个上下文相关联,并且在这个上下文中被命名。双向注入提供了一种别名机制,给内部组件名字(实例变量)创建了上下文别名,允许组件树能够被动态的组装,以及被Seam重新组装。

    Let's start by describing the contexts built in to Seam.

    让我们开始介绍Seam的内置上下文。

    3.1. Seam contexts Seam上下文

    Seam contexts are created and destroyed by the framework. The application does not control context demarcation via explicit Java API calls. Context are usually implicit. In some cases, however, contexts are demarcated via annotations.

    Seam上下文由Seam框架创建和销毁。应用并不显式调用Java API来控制上下文的划分。上下文通常都是隐含的。在某些情况下,上下文通过注释来划分。

    The basic Seam contexts are:

    基本的Seam上下文包括:

    • Stateless context 无状态上下文

    • Event (or request) context 事件(或请求)上下文

    • Page context 页面上下文

    • Conversation context 对话上下文

    • Session context 会话上下文

    • Business process context 业务流程上下文

    • Application context 应用上下文

    You will recognize some of these contexts from servlet and related specifications. However, two of them might be new to you: conversation context, and business process context. One reason state management in web applications is so fragile and error-prone is that the three built-in contexts (request, session and application) are not especially meaningful from the point of view of the business logic.

    你可能很快就能认出一些Servlet和相关规范中的上下文。但是有两个上下文你可能头一次遇到:对话上下文和业务流程上下文。Web应用的状态管理非常脆弱和容易出错的一个原因就是Web内置的上下文(请求,会话和应用)从业务流程的角度上来看并没有特别的意义。

    A user login session, for example, is a fairly arbitrary construct in terms of the actual application work flow. Therefore, most Seam components are scoped to the conversation or business process contexts, since they are the contexts which are most meaningful in terms of the application.

    例如,一个用户登录的会话,按照实际的应用工作流来说,是一个相当武断的构造。因此,大部分Seam组件都被指定在对话或者业务流程的上下文范围内,因为这些上下文更加具有应用意义。

    Let's look at each context in turn.

    让我们来依次看看每个上下文。

    3.1.1. Stateless context 无状态上下文

    Components which are truly stateless (stateless session beans, primarily) always live in the stateless context (this is really a non-context). Stateless components are not very interesting, and are arguably not very object-oriented. Nevertheless, they are important and often useful.

    完全没有状态的组件(主要是无状态会话Bean)总是存在于无状态上下文中(这是完全没有上下文)。无状态组件并不有趣,并且和面向对象的思想相违背。但是,它们非常重要并且非常有用。

    3.1.2. Event context 事件上下文

    The event context is the "narrowest" stateful context, and is a generalization of the notion of the web request context to cover other kinds of events. Nevertheless, the event context associated with the lifecycle of a JSF request is the most important example of an event context, and the one you will work with most often. Components associated with the event context are destroyed at the end of the request, but their state is available and well-defined for at least the lifecycle of the request.

    事件上下文是“最窄”的有状态的上下文,是负责其他类型事件的Web请求上下文的泛化。不过,和一个JSF请求的生命周期相关联的事件上下文是事件上下文的重要例子。这个事件上下文你会经常用到。和事件上下文相关联的组件在请求结束后被销毁,但是至少在请求的生命周期中组件的状态是可以获得的,并且经过了良好的定义。

    When you invoke a Seam component via RMI, or Seam Remoting, the event context is created and destroyed just for the invocation.

    当你通过RMI或者Seam Remoting调用Seam组件时,事件上下文只是为这个调用而被创建和销毁。

    3.1.3. Page context 页面上下文

    The page context allows you to associate state with a particular instance of a rendered page. You can initialize state in your event listener, or while actually rendering the page, and then have access to it from any event that originates from that page. This is especially useful for functionality like clickable lists, where the list is backed by changing data on the server side. The state is actually serialized to the client, so this construct is extremely robust with respect to multi-window operation and the back button.

    页面上下文能够让你将状态和一个已被绘制的页面中特定的实例相关联。你可以在事件监听器中初始化状态,或者实际绘制这个页面的时候初始化这个状态。这对于由在服务器端变化的数据构成的可点击列表这样的功能非常有用。这个状态实际上被序列化发送给客户端,所以这样的构建非常强壮,可以解决多窗口操作和支持回退按钮。

    3.1.4. Conversation context 对话上下文

    The conversation context is a truly central concept in Seam. A conversation is a unit of work from the point of view of the user. It might span several interactions with the user, several requests, and several database transactions. But to the user, a conversation solves a single problem.

    对话上下文是Seam中真正的核心概念。从用户角度出发,一个对话是一个工作单元。它可能跨越多个用户交互,多个请求,多个数据库的事务处理。但是对于用户来说,一个对话只解决一个单一的问题。

    For example, "book hotel", "approve contract", "create order" are all conversations. You might like to think of a conversation implementing a single "use case" or "user story", but the relationship is not necessarily quite exact.

    例如,“旅馆预定”,“合同批准”,“创建订单”都是对话。你肯能认为对话实现了单一的”用例“或者”用户故事“,但是这个关系并不非常确切。

    A conversation holds state associated with "what the user is doing now, in this window". A single user may have multiple conversations in progress at any point in time, usually in multiple windows. The conversation context allows us to ensure that state from the different conversations does not collide and cause bugs.

    一个对话持有关注”在这个窗口中,用户正在干什麽“的状态。一个单一用户可能在任何时间点上使用多个窗口进行多个对话。对话上下文能够让我们确保不同的对话之间不会相互冲突,产生Bug.

    It might take you some time to get used to thinking of applications in terms of conversations. But once you get used to it, we think you'll love the notion, and never be able to not think in terms of conversations again!

    也许你会费些时间来熟悉面向对话的应用。但是一旦一明白并熟悉了这个概念,我想你会喜欢它的,并且,你将不必考虑对话这个事情了。

    Some conversations last for just a single request. Conversations that span multiple requests must be demarcated using annotations provided by Seam.

    一些对话仅仅为了一个单一的请求而存在。跨越多个请求的对话必须通过Seam提供的注释来划分。

    Some conversations are also tasks. A task is a conversation that is significant in terms of a long-running business process, and has the potential to trigger a business process state transition when it is successfully completed. Seam provides a special set of annotations for task demarcation.

    一些对话其实也是任务。任务是一种长时间运行的业务流程的对话,当业务流程成功的完成后,任务能够触发业务流程状态的改变。Seam提供了任务划分的特定一组注释。

    Conversations may be nested, with one conversation taking place "inside" a wider conversation. This is an advanced feature.

    对话能够套嵌对话,一个对话可能被一个更”宽“的对话包含。这是一个高级的特性。

    Usually, conversation state is actually held by Seam in the servlet session between requests. Seam implements configurable conversation timeout, automatically destroying inactive conversations, and thus ensuring that the state held by a single user login session does not grow without bound if the user abandons conversations.

    通常,对话的状态在请求之间Servlet会话中由Seam实际持有。Seam实现了一个可配置的对话超时,能够自动的销毁失活的对话,从而保证了一个单一用户登录会话的状态不会由于用户放弃对话而无限制的增长。

    Seam serializes processing of concurrent requests that take place in the same long-running conversation context, in the same process.

    Seam将位于同一个流程中,同一个长时间运行的对话上下文中的并发请求序列化。

    Alternatively, Seam may be configured to keep conversational state in the client browser.

    另外,Seam也可以配置为在客户端浏览器中保留对话状态。

    3.1.5. Session context 会话上下文

    A session context holds state associated with the user login session. While there are some cases where it is useful to share state between several conversations, we generally frown on the use of session context for holding components other than global information about the logged in user.

    会话上下文持有用户登录会话的状态。虽然有些时候我们需要在不同的对话中共享状态,但通常我们不会同意使用会话上下文来持有组件而不是登录用户的全局信息。

    In a JSR-168 portal environment, the session context represents the portlet session.

    在JSR-168 portal环境中,会话上下文代表portlet会话。

    3.1.6. Business process context 业务流程上下文

    The business process context holds state associated with the long running business process. This state is managed and made persistent by the BPM engine (JBoss jBPM). The business process spans multiple interactions with multiple users, so this state is shared between multiple users, but in a well-defined manner. The current task determines the current business process instance, and the lifecycle of the business process is defined externally using a process definition language, so there are no special annotations for business process demarcation.

    业务流程上下文持有与长时间运行的业务流程相关的状态。这个状态由BPM引擎(JBoss jBPM)来管理和持久化。业务流程跨越多个用户的多个交互,所以这个状态以一种很好的方式在多用户间共享。当前任务决定当前业务流程实例,业务流程的生命周期通过外部的业务定义语言来定义,所以无需为业务流程的划分指定特别的注释。

    3.1.7. Application context 应用上下文

    The application context is the familiar servlet context from the servlet spec. Application context is mainly useful for holding static information such as configuration data, reference data or metamodels. For example, Seam stores its own configuration and metamodel in the application context.

    应用上下文和Servlet规范中的Servlet上下文非常相像。应用上下文主要持有一些静态信息,例如配置数据,引用数据或者元模型。例如,Seam将自己的配置信息和元模型存储在应用上下文中。

    3.1.8. Context variables 上下文变量

    A context defines a namespace, a set of context variables. These work much the same as session or request attributes in the servlet spec. You may bind any value you like to a context variable, but usually we bind Seam component instances to context variables.

    一个上下文定义一个名域,一系列上下文变量。这和Servlet规范中的会话和请求属性是一样的。你可以为一个上下文变量绑定任何值,但通常我们将Seam组件的实例绑定给上下文变量。

    So, within a context, a component instance is identified by the context variable name (this is usually, but not always, the same as the component name). You may programatically access a named component instance in a particular scope via the Contexts class, which provides access to several thread-bound instances of the Context interface:

    所以,在上下文中,一个组件实例通过上下文变量的名字来识别(上下文变量的名字通常和组件名字一样,但并不总是这样)。你可以编程通过Context类来访问某个特定范围的已经命名了的组件实例。Context类提供了很多线程绑定的实例的访问方法。

    User user = (User) Contexts.getSessionContext().get("user");

    You may also set or change the value associated with a name:

    你也可以设置或改变一个名字关联的值:

    Contexts.getSessionContext().set("user", user);

    Usually, however, we obtain components from a context via injection, and put component instances into a context via outjection.

    通常,我们通过注入方式从上下文中获得组件,通过注出方式将组件实例放入上下文。

    3.1.9. Context search priority 上下文搜索优先权

    Sometimes, as above, component instances are obtained from a particular known scope. Other times, all stateful scopes are searched, in priority order. The order is as follows:

    某些时候,例如上面所说的,组件从某个特定的已经知道的范围中获得。但是有些时候,所有有状态的范围都要通过优先队列搜索,优先队列如下:

     

    • Event context  事件上下文

    • Page context  页面上下文

    • Conversation context  对话上下文

    • Session context  会话上下文

    • Business process context  业务流程上下文

    • Application context  应用上下文

    You can perform a priority search by calling Contexts.lookupInStatefulContexts(). Whenever you access a component by name from a JSF page, a priority search occurs.

    你可以调用Contexts.lookupInStatefuleContext()方法进行一个优先搜索。无论你何时在一个JSF页面中通过名字来访问组件,一个优先搜索都会发生。

    3.1.10. Concurrency model 并发模型

    Neither the servlet nor EJB specifications define any facilities for managing concurrent requests originating from the same client. The servlet container simply lets all threads run concurrently and leaves enforcing threadsafeness to application code. The EJB container allows stateless components to be accessed concurrently, and throws an exception if multiple threads access a stateful session bean.

    无论是Servlet还是EJB规范都没有定义任何工具来负责管理从同一个客户端发出的并发请求。Servlet容器简单的让所有线程都并发运行,将线程安全性的工作仍给了应用代码。EJB容器允许并发访问无状态的组件,如果多个线程访问一个有状态的组件的话抛出异常。

    This behavior might have been okay in old-style web applications which were based around fine-grained, synchronous requests. But for modern applications which make heavy use of many fine-grained, asynchronous (AJAX) requests, concurrency is a fact of life, and must be supported by the programming model. Seam weaves a concurrency management layer into its context model.

    这样的行为在旧式Web应用下还可以,旧式Web应用基于细粒度(疑为笔误,应该是粗粒度)和同步请求。但是对于现代应用来说,大量的细粒度和异步请求(AJAX),并发请求是很常见的,需要被编程模型支持。Seam将并发管理层组织进它的上下文模型中。

    The Seam session and application contexts are multithreaded. Seam will allow concurrent requests in a context to be processed concurrently. The event and page contexts are by nature single threaded. The business process context is strictly speaking multi-threaded, but in practice concurrency is sufficiently rare that this fact may be disregarded most of the time. Finally, Seam enforces a single thread per conversation per process model for the conversation context by serializing concurrent requests in the same long-running conversation context.

    The Seam会话和应用上下文是多线程的。Seam允许在一个上下文中的并发请求被并发处理。事件和页面上下文天生就是是单线程的。业务流程上下文严格的讲是多线程的,但是实际应用中,并发的情况非常少,大部分情况下都可以忽略。最后,Seam通过将在同一个长时间运行的对话上下文中的并发请求序列化来强迫实现一个流程一个对话一个单线程的模型。

    Since the session context is multithreaded, and often contains volatile state, session scope components are always protected by Seam from concurrent access. Seam serializes requests to session scope session beans and JavaBeans by default (and detects and breaks any deadlocks that occur). This is not the default behaviour for application scoped components however, since application scoped components do not usually hold volatile state and because synchronization at the global level is extremely expensive. However, you can force a serialized threading model on any session bean or JavaBean component by adding the @Synchronized annotation.

    因为会话上下文是多线程的,经常包含可变的状态,所以Seam总是保护并发访问会话范围的组件。默认情况下,Seam将请求序列话至会话范围的会话Beans和JavaBeans(同时监测和破坏任何死锁)。这个并不是应用范围的组件默认的行为,因为应用范围的组件通常很少持有可变的状态,要知道全局水平上的同步代价非常高昂。虽然如此,你仍然可以在任何会话Bean和JavaBean组件前加上@Synchronized注释,强迫组件使用序列化线程模型。

    This concurrency model means that AJAX clients can safely use volatile session and conversational state, without the need for any special work on the part of the developer.

    这个并发模型意味着AJAX客户端可以安全的使用可变的会话和对话状态,不用增加开发者的任何工作。

    3.2. Seam components  Seam组件

    Seam components are POJOs (Plain Old Java Objects). In particular, they are JavaBeans or EJB 3.0 enterprise beans. While Seam does not require that components be EJBs and can even be used without an EJB 3.0 compliant container, Seam was designed with EJB 3.0 in mind and includes deep integration with EJB 3.0. Seam supports the following component types.

    Seam组件是POJOs(普通的Java对象)。特别的,它们也可以是JavaBeans或者EJB3.0企业Bean。虽然Seam不要求组件是EJB甚至根本不需要EJB3.0兼容的容器,但是Seam是按照EJB3.0设计的,并且非常深的整合了EJB3.0。Seam支持下面的组件类型:

     

    • EJB 3.0 stateless session beans  EJB3.0无状态会话beans

    • EJB 3.0 stateful session beans  EJB3.0有状态会话beans

    • EJB 3.0 entity beans EJB3.0 实体beans

    • JavaBeans JavaBean

    • EJB 3.0 message-driven beans EJB3.0消息beans 

    3.2.1. Stateless session beans 无状态会话beans

    Stateless session bean components are not able to hold state across multiple invocations. Therefore, they usually work by operating upon the state of other components in the various Seam contexts. They may be used as JSF action listeners, but cannot provide properties to JSF components for display.

    Stateless session beans always live in the stateless context.

    Stateless session beans are the least interesting kind of Seam component.

    3.2.2. Stateful session beans

    Stateful session bean components are able to hold state not only across multiple invocations of the bean, but also across multiple requests. Application state that does not belong in the database should usually be held by stateful session beans. This is a major difference between Seam and many other web application frameworks. Instead of sticking information about the current conversation directly in the HttpSession, you should keep it in instance variables of a stateful session bean that is bound to the conversation context. This allows Seam to manage the lifecycle of this state for you, and ensure that there are no collisions between state relating to different concurrent conversations.

    Stateful session beans are often used as JSF action listener, and as backing beans that provide properties to JSF components for display or form submission.

    By default, stateful session beans are bound to the conversation context. They may never be bound to the page or stateless contexts.

    Concurrent requests to session-scoped stateful session beans are always serialized by Seam.

    3.2.3. Entity beans

    Entity beans may be bound to a context variable and function as a seam component. Because entities have a persistent identity in addition to their contextual identity, entity instances are usually bound explicitly in Java code, rather than being instantiated implicitly by Seam.

    Entity bean components do not support bijection or context demarcation. Nor does invocation of an entity bean trigger validation.

    Entity beans are not usually used as JSF action listeners, but do often function as backing beans that provide properties to JSF components for display or form submission. In particular, it is common to use an entity as a backing bean, together with a stateless session bean action listener to implement create/update/delete type functionality.

    By default, entity beans are bound to the conversation context. They may never be bound to the stateless context.

    Note that it in a clustered environment is somewhat less efficient to bind an entity bean directly to a conversation or session scoped Seam context variable than it would be to hold a reference to the entity bean in a stateful session bean. For this reason, not all Seam applications define entity beans to be Seam components.

    3.2.4. JavaBeans

    Javabeans may be used just like a stateless or stateful session bean. However, they do not provide the functionality of a session bean (declarative transaction demarcation, declarative security, efficient clustered state replication, EJB 3.0 persistence, timeout methods, etc).

    In a later chapter, we show you how to use Seam and Hibernate without an EJB container. In this use case, components are JavaBeans instead of session beans. Note, however, that in many application servers it is somewhat less efficient to cluster conversation or session scoped Seam JavaBean components than it is to cluster stateful session bean components.

    By default, JavaBeans are bound to the event context.

    Concurrent requests to session-scoped JavaBeans are always serialized by Seam.

    3.2.5. Message-driven beans

    Message-driven beans may function as a seam component. However, message-driven beans are called quite differently to other Seam components - instead of invoking them via the context variable, they listen for messages sent to a JMS queue or topic.

    Message-driven beans may not be bound to a Seam context. Nor do they have access to the session or conversation state of their "caller". However, they do support bijection and some other Seam functionality.

    3.2.6. Interception 拦截

    In order to perform its magic (bijection, context demarcation, validation, etc), Seam must intercept component invocations. For JavaBeans, Seam is in full control of instantiation of the component, and no special configuration is needed. For entity beans, interception is not required since bijection and context demarcation are not defined. For session beans, we must register an EJB interceptor for the session bean component. We could use an annotation, as follows:

    为了完成一些Seam的魔力(双向注射,上下文划分,验证等等),Seam必须拦截组件调用。对于JavaBeans,Seam完全控制了组件的实例化,不需要任何特定的配置。对于实体beans,没有必要进行拦截,因为没有定义双向注射和上下文划分。对于会话beans,我们必须为会话bean组件注册EJB拦截器。我们使用注释来实现这一点:

    @Stateless
    @Interceptors(SeamInterceptor.class)
    public class LoginAction implements Login {
    ...
    }

    But a much better way is to define the interceptor in ejb-jar.xml.

    然而一个更好的方式是在ejb-jar.xml中定义拦截器。

    <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>

    3.2.7. Component names 组件的名称

    All seam components need a name. We can assign a name to a component using the @Name annotation:

    所有Seam组件都要有名称,我们通过@Name注释给组件命名:

    @Name("loginAction")
    @Stateless
    public class LoginAction implements Login {
    ...
    }

    This name is the seam component name and is not related to any other name defined by the EJB specification. However, seam component names work just like JSF managed bean names and you can think of the two concepts as identical.

    这个名字是seam组件的名字和其他EJB规范定义的名字没有任何关系。但是,seam组件名字就像JSF管理bean的名字一样,这两个概念是一样的。

    @Name is not the only way to define a component name, but we always need to specify the name somewhere. If we don't, then none of the other Seam annotations will function.

    @Name不是唯一定义组件名字的方法,但是我们总要在某个地方指定名字。如果我们不这么做的话,其他seam注释也不会工作

    Just like in JSF, a seam component instance is usually bound to a context variable with the same name as the component name. So, for example, we would access the LoginAction using Contexts.getStatelessContext().get("loginAction"). In particular, whenever Seam itself instantiates a component, it binds the new instance to a variable with the component name. However, again like JSF, it is possible for the application to bind a component to some other context variable by programmatic API call. This is only useful if a particular component serves more than one role in the system. For example, the currently logged in User might be bound to the currentUser session context variable, while a User that is the subject of some administration functionality might be bound to the user conversation context variable.

    和JSF一样,一个seam组件的实例通常和一个同名的上下文变量绑定。例如我们使用Context.getStatelessContext().get("loginAction")方法来访问LoginAction.特别的,无论何时Seam本身初始化一个组件,都将这个新组件与同名的变量绑定。然而又和JSF一样,你可以通过程序API调用将组件和其他上下文变量绑定。这只有在一个组件有多个角色的系统中才有用处。例如当前登录的User可以绑定在currentUser会话上下文变量中,但是一个User也可以绑定到user对话上下文变量,如果这个User负责一些管理功能。

    For very large applications, and for built-in seam components, qualified names are often used.

    对于大型应用和内置的seam组件,通常使用符合标准的名字。

    @Name("com.jboss.myapp.loginAction")
    @Stateless
    public class LoginAction implements Login {
    ...
    }

    We may use the qualified component name both in Java code and in JSF's expression language:

    我们可以在Java代码和JSF表达语言中都是用符合标准的组件名字:

    <h:commandButton type="submit" value="Login"
    action="#{com.jboss.myapp.loginAction.login}"/>

    Since this is noisy, Seam also provides a means of aliasing a qualified name to a simple name. Add a line like this to the components.xml file:

    这个有点讨厌,Seam也提供了别名机制让你可以给符合标准的名字赋别名。例如在component.xml文件中添加一行:

    <factory name="loginAction" scope="STATELESS" value="#{com.jboss.myapp.loginAction}"/>

    All of the built-in Seam components have qualified names, but most of them are aliased to a simple name by the components.xml file included in the Seam jar.

    所有内置的Seam组件都有符合标准的名字,但是大部分都拥有别名,这些配置在Seam jar文件中的component.xml文件中。

    3.2.8. Defining the component scope 定义组件范围

    We can override the default scope (context) of a component using the @Scope annotation. This lets us define what context a component instance is bound to, when it is instantiated by Seam.

    我们可以使用@Scope注释来覆盖组件的默认范围(上下文)。这允许我们在Seam实例化组件实例的时候设置组件实例的上下文。

    @Name("user")
    @Entity
    @Scope(SESSION)
    public class User {
    ...
    }

    org.jboss.seam.ScopeType defines an enumeration of possible scopes.

    3.2.9. Components with multiple roles 拥有多个角色的组件

    Some Seam component classes can fulfill more than one role in the system. For example, we often have a User class which is usually used as a session-scoped component representing the current user but is used in user administration screens as a conversation-scoped component. The @Role annotation lets us define an additional named role for a component, with a different scope—it lets us bind the same component class to different context variables. (Any Seam component instance may be bound to multiple context variables, but this lets us do it at the class level, and take advantage of auto-instantiation.)

    一些Seam组件类在系统中可以充当多个角色。例如,我们通常会有一个User类,这个类通常在会话范围内代表当前用户,但是在用户管理界面中也用到这个类作为对话范围组件。@Role注释让我们能够为组件定义一个附加的角色,可以有不同的范围,这样一个组件类可以绑定到不同的上下文变量中。(任何Seam组件实例可以和多个上下文变量绑定,但是这个注释让我们在类的级别上实现,充分利用了自动实例化的好处)。

    @Name("user")
    @Entity
    @Scope(CONVERSATION)
    @Role(name="currentUser", scope=SESSION)
    public class User {
    ...
    }

    The @Roles annotation lets us specify as many additional roles as we like.

    @Roles注释能让我们指定多个角色

    @Name("user")
    @Entity
    @Scope(CONVERSATION)
    @Roles({@Role(name="currentUser", scope=SESSION),
    @Role(name="tempUser", scope=EVENT)})
    public class User {
    ...
    }

    3.2.10. Built-in components 内置的组件

    Like many good frameworks, Seam eats its own dogfood and is implemented mostly as a set of built-in Seam interceptors (see later) and Seam components. This makes it easy for applications to interact with built-in components at runtime or even customize the basic functionality of Seam by replacing the built-in components with custom implementations. The built-in components are defined in the Seam namespace org.jboss.seam.core and the Java package of the same name.

    和其他不错的框架一样,Seam干了他自己该干的事情,实现了一套内置的Seam拦截器(参见后面)和Seam组件。这让应用能够非常容易的在运行时和内置组件交互,甚至通过使用定制的组件替换内置的组件来定制基本功能。内置的组件在Seam名域org.jboss.seam.core中定义,Java包有相同的名字。

    The built-in components may be injected, just like any Seam components, but they also provide convenient static instance() methods:

    内置的组件可以被注入,就像任何Seam组件一样,但是他们也提供了方便的instance()静态方法。

    FacesMessages.instance().add("Welcome back, #{user.name}!");

    3.3. Bijection 双向注射

    Dependency injection or inversion of control is by now a familiar concept to most Java developers. Dependency injection allows a component to obtain a reference to another component by having the container "inject" the other component to a setter method or instance variable.

    现在每个Java开发者都非常熟悉依赖注入或反转控制这些概念。依赖注入允许一个组件通过容器”注入“的方式获得另一个组件的引用,容器将另外的的组件注入setter或者实例变量中。

    In all dependency injection implementations that we have seen, injection occurs when the component is constructed, and the reference does not subsequently change for the lifetime of the component instance. For stateless components, this is reasonable. From the point of view of a client, all instances of a particular stateless component are interchangeable.

    在我们看到的所有依赖注入的实现中,注入发生在组件被构造时,在组件实例的生命周期内引用不能改变。对于无状态的组件来说,这是有道理的。从客户角度出发,一个特定的无状态的组件的所有实例都可以互换。

    On the other hand, Seam emphasizes the use of stateful components. So traditional dependency injection is no longer a very useful construct. Seam introduces the notion of bijection as a generalization of injection. In contrast to injection, bijection is:

    另一方面,Seam强调使用有状态的组件。所以传统的依赖注入不再是非常有用的构造了。Seam引入了注入的泛化概念双向注射。对比注入,双向注射是:

     

    • contextual - bijection is used to assemble stateful components from various different contexts (a component from a "wider" context may even have a reference to a component from a "narrower" context)

    • 上下文相关的-双向注射用来从各种不同的上下文中(一个来自”更宽“的上下文的组件可以拥有一个来自比它“更窄”的上下文中的组件的引用)组装有状态的组件 。

    • bidirectional - values are injected from context variables into attributes of the component being invoked, and also outjected from the component attributes back out to the context, allowing the component being invoked to manipulate the values of contextual variables simply by setting its own instance variables

    • 双向-值从上下文变量中注入到被调用的组件的属性中,并且可以从组件的属性中注出回上下文,允许组件被调用,通过简单的设置自己的实例变量来操作上下文变量的值

    • dynamic - since the value of contextual variables changes over time, and since Seam components are stateful, bijection takes place every time a component is invoked

    • 动态的-因为上下文变量的值随着时间而变化,还因为Seam组件是有状态的,每当一个组件被调用式都会发生双向注射

    In essence, bijection lets you alias a context variable to a component instance variable, by specifying that the value of the instance variable is injected, outjected, or both. Of course, we use annotations to enable bijection.

    本质上说,双向注射能够让你通过指定实例变量的值是注入,还是注出或者两者都是,从而将一个上下文变量别名为一个组件的实例变量。当然,我们使用注释来完成双向注射。

    The @In annotation specifies that a value should be injected, either into an instance variable:

    @In注释指定一个值应该被注入到一个实例变量

    @Name("loginAction")
    @Stateless
    public class LoginAction implements Login {
    @In User user;
    ...
    }

    or into a setter method:

    或者注入到一个setter方法中:

    @Name("loginAction")
    @Stateless
    public class LoginAction implements Login {
    User user;

    @In
    public void setUser(User user) {
    this.user=user;
    }

    ...
    }

    By default, Seam will do a priority search of all contexts, using the name of the property or instance variable that is being injected. You may wish to specify the context variable name explicitly, using, for example, @In("currentUser").

    在默认情况下,Seam将使用注入的实例或者属性的名字对所有的上下文进行优先搜索。你可以显式的指定上下文变量的名字。例如@In("currentUser").

    If you want Seam to create an instance of the component when there is no existing component instance bound to the named context variable, you should specify @In(create=true). If the value is optional (it can be null), specify @In(required=false).

    For some components, it can be repetitive to have to specify @In(create=true) everywhere they are used. In such cases, you can annotate the component @AutoCreate, and then it will always be created, whenever needed, even without the explicit use of create=true.

    You can even inject the value of an expression:

    @Name("loginAction")
    @Stateless
    public class LoginAction implements Login {
    @In("#{user.username}") String username;
    ...
    }

    (There is much more information about component lifecycle and injection in the next chapter.)

    The @Out annotation specifies that an attribute should be outjected, either from an instance variable:

    @Name("loginAction")
    @Stateless
    public class LoginAction implements Login {
    @Out User user;
    ...
    }

    or from a getter method:

    @Name("loginAction")
    @Stateless
    public class LoginAction implements Login {
    User user;

    @Out
    public User getUser() {
    return user;
    }

    ...
    }

    An attribute may be both injected and outjected:

    @Name("loginAction")
    @Stateless
    public class LoginAction implements Login {
    @In @Out User user;
    ...
    }

    or:

    @Name("loginAction")
    @Stateless
    public class LoginAction implements Login {
    User user;

    @In
    public void setUser(User user) {
    this.user=user;
    }

    @Out
    public User getUser() {
    return user;
    }

    ...
    }

    3.4. Lifecycle methods

    Session bean and entity bean Seam components support all the usual EJB 3.0 lifecycle callback (@PostConstruct, @PreDestroy, etc). But Seam also supports the use of any of these callbacks with JavaBean components. However, since these annotations are not available in a J2EE environment, Seam defines two additional component lifecycle callbacks, equivalent to @PostConstruct and @PreDestroy.

    The @Create method is called after Seam instantiates a component. Components may define only one @Create method.

    The @Destroy method is called when the context that the Seam component is bound to ends. Components may define only one @Destroy method.

    In addition, stateful session bean components must define a method with no parameters annotated @Remove. This method is called by Seam when the context ends.

    Finally, a related annotation is the @Startup annotation, which may be applied to any application or session scoped component. The @Startup annotation tells Seam to instantiate the component immediately, when the context begins, instead of waiting until it is first referenced by a client. It is possible to control the order of instantiation of startup components by specifying @Startup(depends={....}).

    3.5. Conditional installation

    The @Install annotation lets you control conditional installation of components that are required in some deployment scenarios and not in others. This is useful if:

    • You want to mock out some infrastructural component in tests.

    • You want change the implementation of a component in certain deployment scenarios.

    • You want to install some components only if their dependencies are available (useful for framework authors).

    @Install works by letting you specify precedence and dependencies.

    The precedence of a component is a number that Seam uses to decide which component to install when there are multiple classes with the same component name in the classpath. Seam will choose the component with the higher precendence. There are some predefined precedence values (in ascending order):

    1. BUILT_IN — the lowest precedece components are the components built in to Seam.

    2. FRAMEWORK — components defined by third-party frameworks may override built-in components, but are overridden by application components.

    3. APPLICATION — the default precedence. This is appropriate for most application components.

    4. DEPLOYMENT — for application components which are deployment-specific.

    5. MOCK — for mock objects used in testing.

    Suppose we have a component named messageSender that talks to a JMS queue.

    @Name("messageSender") 
    public class MessageSender {
    public void sendMessage() {
    //do something with JMS
    }
    }

    In our unit tests, we don't have a JMS queue available, so we would like to stub out this method. We'll create a mock component that exists in the classpath when unit tests are running, but is never deployed with the application:

    @Name("messageSender") 
    @Install(precedence=MOCK)
    public class MockMessageSender extends MessageSender {
    public void sendMessage() {
    //do nothing!
    }
    }

    The precedence helps Seam decide which version to use when it finds both components in the classpath.

    This is nice if we are able to control exactly which classes are in the classpath. But if I'm writing a reusable framework with many dependecies, I don't want to have to break that framework across many jars. I want to be able to decide which components to install depending upon what other components are installed, and upon what classes are available in the classpath. The @Install annotation also controls this functionality. Seam uses this mechanism internally to enable conditional installation of many of the built-in components. However, you probably won't need to use it in your application.

    3.6. Logging

    Who is not totally fed up with seeing noisy code like this?

    private static final Log log = LogFactory.getLog(CreateOrderAction.class);

    public Order createOrder(User user, Product product, int quantity) {
    if ( log.isDebugEnabled() ) {
    log.debug("Creating new order for user: " + user.username() +
    " product: " + product.name()
    + " quantity: " + quantity);
    }
    return new Order(user, product, quantity);
    }

    It is difficult to imagine how the code for a simple log message could possibly be more verbose. There is more lines of code tied up in logging than in the actual business logic! I remain totally astonished that the Java community has not come up with anything better in 10 years.

    Seam provides a logging API that simplifies this code significantly:

    @Logger private Log log;

    public Order createOrder(User user, Product product, int quantity) {
    log.debug("Creating new order for user: #0 product: #1 quantity: #2", user.username(), product.name(), quantity);
    return new Order(user, product, quantity);
    }

    It doesn't matter if you declare the log variable static or not—it will work either way, except for entity bean components which require the log variable to be static.

    Note that we don't need the noisy if ( log.isDebugEnabled() ) guard, since string concatenation happens inside the debug() method. Note also that we don't usually need to specify the log category explicitly, since Seam knows what component it is injecting the Log into.

    If User and Product are Seam components available in the current contexts, it gets even better:

    @Logger private Log log;

    public Order createOrder(User user, Product product, int quantity) {
    log.debug("Creating new order for user: #{user.username} product: #{product.name} quantity: #0", quantity);
    return new Order(user, product, quantity);
    }

    Seam logging automagically chooses whether to send output to log4j or JDK logging. If log4j is in the classpath, Seam with use it. If it is not, Seam will use JDK logging.

    3.7. The Mutable interface and @ReadOnly

    Many application servers feature an amazingly broken implementation of HttpSession clustering, where changes to the state of mutable objects bound to the session are only replicated when the application calls setAttribute() explicitly. This is a source of bugs that can not effectively be tested for at development time, since they will only manifest when failover occurs. Furthermore, the actual replication message contains the entire serialized object graph bound to the session attribute, which is inefficient.

    Of course, EJB stateful session beans must perform automatic dirty checking and replication of mutable state and a sophisticated EJB container can introduce optimizations such as attribute-level replication. Unfortunately, not all Seam users have the good fortune to be working in an environment that supports EJB 3.0. So, for session and conversation scoped JavaBean and entity bean components, Seam provides an extra layer of cluster-safe state management over the top of the web container session clustering.

    For session or conversation scoped JavaBean components, Seam automatically forces replication to occur by calling setAttribute() once in every request that the component was invoked by the application. Of course, this strategy is inefficient for read-mostly components. You can control this behavior by implementing the org.jboss.seam.core.Mutable interface, or by extending org.jboss.seam.core.AbstractMutable, and writing your own dirty-checking logic inside the component. For example,

    @Name("account")
    public class Account extends AbstractMutable
    {
    private BigDecimal balance;

    public void setBalance(BigDecimal balance)
    {
    setDirty(this.balance, balance);
    this.balance = balance;
    }

    public BigDecimal getBalance()
    {
    return balance;
    }

    ...

    }

    Or, you can use the @ReadOnly annotation to achieve a similar effect:

    @Name("account")
    public class Account
    {
    private BigDecimal balance;

    public void setBalance(BigDecimal balance)
    {
    this.balance = balance;
    }

    @ReadOnly
    public BigDecimal getBalance()
    {
    return balance;
    }

    ...

    }

    For session or conversation scoped entity bean components, Seam automatically forces replication to occur by calling setAttribute() once in every request, unless the (conversation-scoped) entity is currently associated with a Seam-managed persistence context, in which case no replication is needed. This strategy is not necessarily efficient, so session or conversation scope entity beans should be used with care. You can always write a stateful session bean or JavaBean component to "manage" the entity bean instance. For example,

    @Stateful
    @Name("account")
    public class AccountManager extends AbstractMutable
    {
    private Account account; // an entity bean

    @Unwrap
    public void getAccount()
    {
    return account;
    }

    ...

    }

    Note that the EntityHome class in the Seam Application Framework provides a great example of managing an entity bean instance using a Seam component.

    3.8. Factory and manager components

    We often need to work with objects that are not Seam components. But we still want to be able to inject them into our components using @In and use them in value and method binding expressions, etc. Sometimes, we even need to tie them into the Seam context lifecycle (@Destroy, for example). So the Seam contexts can contain objects which are not Seam components, and Seam provides a couple of nice features that make it easier to work with non-component objects bound to contexts.

    The factory component pattern lets a Seam component act as the instantiator for a non-component object. A factory method will be called when a context variable is referenced but has no value bound to it. We define factory methods using the @Factory annotation. The factory method binds a value to the context variable, and determines the scope of the bound value. There are two styles of factory method. The first style returns a value, which is bound to the context by Seam:

    @Factory(scope=CONVERSATION)
    public List<Customer> getCustomerList() {
    return ... ;
    }

    The second style is a method of type void which binds the value to the context variable itself:

    @DataModel List<Customer> customerList;

    @Factory("customerList")
    public void initCustomerList() {
    customerList = ... ;
    }

    In both cases, the factory method is called when we reference the customerList context variable and its value is null, and then has no further part to play in the lifecycle of the value. An even more powerful pattern is the manager component pattern. In this case, we have a Seam component that is bound to a context variable, that manages the value of the context variable, while remaining invisible to clients.

    A manager component is any component with an @Unwrap method. This method returns the value that will be visable to clients, and is called every time a context variable is referenced.

    @Name("customerList")
    @Scope(CONVERSATION)
    public class CustomerListManager
    {
    ...

    @Unwrap
    public List<Customer> getCustomerList() {
    return ... ;
    }
    }

    The manager component pattern is especially useful if we have an object where you need more control over the lifecycle of the component. For example, if you have a heavyweight object that needs a cleanup operation when the context ends you could @Unwrap the object, and perform cleanup in the @Destroy method of the manager component.

    @Name("hens")
    @Scope(APPLICATION)
    public class HenHouse {

    private Set<Hen> hens;

    @Unwrap
    public List<Hen> getHens() {
    if (hens == null) {
    // Setup our hens
    }
    return hens;
    }

    @Observer({"chickBorn", "chickenBoughtAtMarket"})
    public addHen() {
    hens.add(hen);
    }

    @Observer("chickenSoldAtMarket")
    public removeHen() {
    hens.remove(hen);
    }

    @Observer("foxGetsIn")
    public addHen() {
    hens.clear();
    }
    ...
    }

    Here the managed component observes many events which change the underlying object. The component manages these actions itself, and because the object is unwrapped on every access, a consistent view is provided.


    Prev  Up  Next
    Chapter 2. Getting started with Seam, using seam-gen  Home  Chapter 4. Configuring Seam components
    August 28

    JBoss Seam: Introduction

    Introduction to JBoss Seam
    Prev   Next

    Introduction to JBoss Seam   JBoss Seam介绍

    Seam is an application framework for Enterprise Java. It is inspired by the following principles:

    Seam是一种企业级Java的应用框架。它的灵感来源于以下原理或技术:

    One kind of "stuff"  只有一种工具

    Seam defines a uniform component model for all business logic in your application. A Seam component may be stateful, with the state associated with any one of several well-defined contexts, including the long-running, persistent, business process context and the conversation context, which is preserved across multiple web requests in a user interaction.

    Seam为你的应用程序中所有的商业逻辑定义了一种统一的组件模型。一个Seam组件可能是有状态的,这种状态可以和任何定义良好的上下文相关联,这些上下文包括长时间运行上下文,持久化上下文,商业过程上下文和在用户交互中能够跨越多个Web请求的会话上下文。

    There is no distinction between presentation tier components and business logic components in Seam. You can layer your application according to whatever architecture you devise, rather than being forced to shoehorn your application logic into an unnatural layering scheme forced upon you by whatever combination of stovepipe frameworks you're using today.

    Seam中的组件并不区分表现层组件和业务逻辑组件。你可以根据任何你设计的体系来将你的应用程序分层,而非必须削足适履的将你的程序逻辑被迫按照某种分层机制才能和你目前使用的框架相结合。

    Unlike plain Java EE or J2EE components, Seam components may simultaneously access state associated with the web request and state held in transactional resources (without the need to propagate web request state manually via method parameters). You might object that the application layering imposed upon you by the old J2EE platform was a Good Thing. Well, nothing stops you creating an equivalent layered architecture using Seam—the difference is that you get to architect your own application and decide what the layers are and how they work together.

              和普通的Java EE或者J2EE组件不同,Seam组件可以同时访问Web请求相关的状态和事务性资源持有的状态(不必手工的通过方法参数来传递Web请求的状态).你可能不认为在旧的J2EE平台上加入一个应用层是一种好办法。然而,你仍可以使用Seam来实现一个相同的应用层体系-区别在于,你需要架构你自己的应用并且决定需要哪些层以及它们如何协同。

    Integrate JSF with EJB 3.0 将JSF和EJB3.0整合

    JSF and EJB 3.0 are two of the best new features of Java EE 5. EJB3 is a brand new component model for server side business and persistence logic. Meanwhile, JSF is a great component model for the presentation tier. Unfortunately, neither component model is able to solve all problems in computing by itself. Indeed, JSF and EJB3 work best used together. But the Java EE 5 specification provides no standard way to integrate the two component models. Fortunately, the creators of both models foresaw this situation and provided standard extension points to allow extension and integration with other frameworks.

    JSF和EJB3.0是Java EE 5中最好的两个特性。EJB3是一个新的服务器端的业务和持久化组件模型。同时JSF是一种很棒的表现层组件。可惜的是,这两个组件模型都没有办法单独解决所有问题。实际上,JSF和EJB3可以完美结合。但是JavaEE5规范并没有提供这两种组件模型如何整合的标准。幸运的是,这两种模型的创建者预见了这种情况并且提供了标准的扩展点,从而我们能够通过扩展来与其他框架整合。

    Seam unifies the component models of JSF and EJB3, eliminating glue code, and letting the developer think about the business problem.

    Seam统一了JSF和EJB3的组件模型,减少了整合代码(意思是为了让这两种模型相互沟通而必须编写的额外代码),从而能够让开发者专心于业务问题

    It is possible to write Seam applications where "everything" is an EJB. This may come as a surprise if you're used to thinking of EJBs as coarse-grained, so-called "heavyweight" objects. However, version 3.0 has completely changed the nature of EJB from the point of view of the developer. An EJB is a fine-grained object—nothing more complex than an annotated JavaBean. Seam even encourages you to use session beans as JSF action listeners!

    你可以写出一个完全是EJB组成的Seam应用。如果你以前认为EJB是粗粒度,重量级的家伙,你肯定会感到惊讶。实际上,EJB的3.0版本完全的改变了原始设计,从开发折角度来说,完全颠覆了旧的EJB体系。现在的EJB是一种细粒度的轻量级的对象-并不比使用注释的JavaBean复杂多少。Seam甚至鼓励你将会话Bean作为JSF动作的监听器。

    On the other hand, if you prefer not to adopt EJB 3.0 at this time, you don't have to. Virtually any Java class may be a Seam component, and Seam provides all the functionality that you expect from a "lightweight" container, and more, for any component, EJB or otherwise.

              另一方面,如果你不想采用EJB3.0, 完全可以。 实际上任何Java类都可以成为Seam的组件,Seam提供了你          期望的一个轻量级容器能够提供的所有功能,并且提供了任何组件,包括EJB或其他的组件的所有功能。

    Integrated AJAX 和AJAX的整合

    Seam supports the best open source JSF-based AJAX solutions: JBoss RichFaces and ICEfaces. These solutions let you add AJAX capability to your user interface without the need to write any JavaScript code.

               Seam支持最棒的基于JSF的开源AJAX解决方案:JBoss RichFaces和ICEfaces. 这些解决方案能够让你根           本不用编写任何JavaScript代码的情况下,让你的用户体会到最棒的AJAX体验。

    Alternatively, Seam provides a built-in JavaScript remoting layer that lets you call components asynchronously from client-side JavaScript without the need for an intermediate action layer. You can ever subscribe to server-side JMS topics and receive messages via AJAX push.

    另外,Seam提供了内置的JavaScript远端层让你能够从客户端异步的调用组件,不需要通过动作层做中介。你可以随时订阅服务器端的JMS主题和通过AJAX push来接收消息。

    Neither of these approaches would work well, were it not for Seam's built-in concurrency and state management, which ensures that many concurrent fine-grained, asynchronous AJAX requests are handled safely and efficiently on the server side.

              这些方法能够很好的工作完全取决于Seam内置的并发和状态管理机制,它们保证了并发的细粒度,异步                AJAX请求能够安全并且有效的在服务器端被处理。

    Business process as a first class construct 一流的业务过程构建

    Optionally, Seam provides transparent business process management via jBPM. You won't believe how easy it is to implement complex workflows, collaboration and and task management using jBPM and Seam.

    Seam通过jBPM提供了透明的业务过程管理。你甚至不会相信通过jBPM和Seam能够这么简单的实现复杂的工作流以及协同和任务管理功能。

    Seam even allows you to define presentation tier pageflow using the same language (jPDL) that jBPM uses for business process definition.

    Seam甚至能够让你通过jBPM使用的业务过程定义语言(jPDL)来定义表现层的页面流。

    JSF provides an incredibly rich event model for the presentation tier. Seam enhances this model by exposing jBPM's business process related events via exactly the same event handling mechanism, providing a uniform event model for Seam's uniform component model.

              JSF提供了不可思议的表现层富事件模型。通过以通用事件处理机制一样的方式暴露jBPM的业务过程相关的          事件,Seam增强了这一富事件模型,为Seam统一的组件模型提供了一种统一的事件模型。

    Declarative state management 声明式的状态管理

    We're all used to the concept of declarative transaction management and declarative security from the early days of EJB. EJB 3.0 even introduces declarative persistence context management.

               我们在早期的EJB中非常熟悉声明式的事务管理和安全管理的概念。EJB3.0甚至引入了声明式的持久性上           下文管理。

    These are three examples of a broader problem of managing state that is associated with a particular context, while ensuring that all needed cleanup occurs when the context ends. Seam takes the concept of declarative state management much further and applies it to application state. Traditionally, J2EE applications implement state management manually, by getting and setting servlet session and request attributes.

              。。。。。。。Seam延伸了声明式状态管理的概念,提出了应用状态。传统的J2EE应用通过getting和           setting Servlet会话和请求属性来手工实现状态管理。          理

     This approach to state management is the source of many bugs and memory leaks when applications fail to clean up session attributes, or when session data associated with different workflows collides in a multi-window application. Seam has the potential to almost entirely eliminate this class of bugs.

    但是通过这种方式来进行状态管理非常容易造成很多Bug和内存泄漏问题,特别是当应用程序清除会话属性失败或者和在多窗口应用中不同工作流相关联的会话数据冲突时。Seam能够完全避免这种类型的Bug.

    Declarative application state management is made possible by the richness of the context model defined by Seam. Seam extends the context model defined by the servlet spec—request, session, application—with two new contexts—conversation and business process—that are more meaningful from the point of view of the business logic.

    通过Seam定义的丰富的上下文模型,Seam能够进行声明式的应用状态管理。Seam扩展了Servlet规范-请求,会话,应用-加入了两个新的上下文-会话和业务过程,从业务逻辑的角度来说,这样更具意义。

    You'll be amazed at how many things become easier once you start using conversations. Have you ever suffered pain dealing with lazy association fetching in an ORM solution like Hibernate or JPA? Seam's conversation-scoped persistence contexts mean you'll rarely have to see a LazyInitializationException. Have you ever had problems with the refresh button? The back button? With duplicate form submission? With propagating messages across a post-then-redirect? Seam's conversation management solves these problems without you even needing to really think about them. They're all symptoms of the broken state management architecture has been prevalent since the earliest days of the web.

    Bijection 双向注射

    The notion of Inversion of Control or dependency injection exists in both JSF and EJB3, as well as in numerous so-called "lightweight containers". Most of these containers emphasize injection of components that implement stateless services.

             JSF和EJB3中都存在反转控制或依赖注入的概念,其他的一些所谓“轻量级容器“也都有这些概念。大部分这         些容器都强调实现了无状态服务的组件的注入。

    Even when injection of stateful components is supported (such as in JSF), it is virtually useless for handling application state because the scope of the stateful component cannot be defined with sufficient flexibility, and because components belonging to wider scopes may not be injected into components belonging to narrower scopes.

    虽然有状态的组件注入被支持(例如JSF),其实它在应用状态的处理中毫无用处,因为我们无法足够灵活的定义有状态的组件的作用范围,并且作用范围大的组件不能注入到作用范围小的组件中去。

    Bijection differs from IoC in that it is dynamic, contextual, and bidirectional. You can think of it as a mechanism for aliasing contextual variables (names in the various contexts bound to the current thread) to attributes of the component. Bijection allows auto-assembly of stateful components by the container. It even allows a component to safely and easily manipulate the value of a context variable, just by assigning to an attribute of the component.

              双向注射和反转控制不同,它是动态的,具有上下文的,并且是双向的。你可以将它想象成一种组件属性的         上下文变量的别名机制(不同上下文中的名字进入到当前线程中)。双向注射能够通过容器自动组装有状态         的组件。它甚至允许一个组件仅仅通过属性赋值的方式安全并且简单的操作一个上下文变量的值。

    Workspace management and multi-window browsing 工作域管理和多窗口浏览

    Seam applications let the user freely switch between multiple browser tabs, each associated with a different, safely isolated, conversation. Applications may even take advantage of workspace management, allowing the user to switch between conversations (workspaces) in a single browser tab. Seam provides not only correct multi-window operation, but also multi-window-like operation in a single window!

              Seam应用能够让用户在多个浏览器窗口间切换,每个窗口和一个不同的安全隔离的对话相关联。应用甚至          利用工作域管理机制,允许用户在单一的浏览器窗口中的不同的对话(工作域)间切换。Seam不仅仅提供          正确的多窗口操作,并且在单一窗口中也能够正确处理多窗口形式的操作。

    Prefer annotations to XML  使用注释,而不是XML

    Traditionally, the Java community has been in a state of deep confusion about precisely what kinds of meta-information counts as configuration. J2EE and popular "lightweight" containers have provided XML-based deployment descriptors both for things which are truly configurable between different deployments of the system, and for any other kinds or declaration which can not easily be expressed in Java. Java 5 annotations changed all this.

    EJB 3.0 embraces annotations and "configuration by exception" as the easiest way to provide information to the container in a declarative form. Unfortunately, JSF is still heavily dependent on verbose XML configuration files. Seam extends the annotations provided by EJB 3.0 with a set of annotations for declarative state management and declarative context demarcation. This lets you eliminate the noisy JSF managed bean declarations and reduce the required XML to just that information which truly belongs in XML (the JSF navigation rules).

    Integration testing is easy 整合测试变简单了

    Seam components, being plain Java classes, are by nature unit testable. But for complex applications, unit testing alone is insufficient. Integration testing has traditionally been a messy and difficult task for Java web applications. Therefore, Seam provides for testability of Seam applications as a core feature of the framework.

              Seam组件本身是普通的Java类,自然非常适合单元测试。但是对于复杂的应用来说,单元测试是不够的。
    整合测试曾经是Java Web应用中非常痛苦和困难的任务。因此,Seam将整合测试作为整个应用框架中重要的核心特征。

    You can easily write JUnit or TestNG tests that reproduce a whole interaction with a user, exercising all components of the system apart from the view (the JSP or Facelets page). You can run these tests directly inside your IDE, where Seam will automatically deploy EJB components using JBoss Embedded.

              你可以简单的编写JUnit或TestNG测试,模拟和用户的整个交互,剥离视图(JSP或Facelets页面)测试系统          所有的组件。你可以直接在你的IDE中运行测试,Seam使用JBoss Embedded自动将EJB组件部署。

    The specs ain't perfect 规范总是不够完美

    We think the latest incarnation of Java EE is great. But we know it's never going to be perfect. Where there are holes in the specifications (for example, limitations in the JSF lifecycle for GET requests), Seam fixes them. And the authors of Seam are working with the JCP expert groups to make sure those fixes make their way back into the next revision of the standards.

             我们觉得最近的Java EE规范不错。但是它远远称不上完美。规范中到处都有漏洞(例如,JSF生命周期对于         GET请求具有很大的限制),Seam矫正了这些漏洞。Seam的创建者和JCP的专家组一起工作,将在下一个         标准修订版本中矫正它们。

    There's more to a web application than serving HTML pages Web应用不仅仅是HTML页面

    Today's web frameworks think too small. They let you get user input off a form and into your Java objects. And then they leave you hanging. A truly complete web application framework should address problems like persistence, concurrency, asynchronicity, state management, security, email, messaging, PDF and chart generation, workflow, wikitext rendering, webservices, caching and more. Once you scratch the surface of Seam, you'll be amazed at how many problems become simpler...

    当前的Web框架的视野太小。这些框架只是让你获得用户的输入,将它们传入你的Java对象。然后它们消失了。一个真正的Web应用框架应该负责解决诸如持久性,并发性,异步性,状态管理,安全,电子邮件,消息服务,PDF,图表绘制,工作流,Wiki文本绘制,Web服务,缓存和更多的问题。一旦你尝试了Seam,你会惊奇的发现这些问题非常简单...

    Seam integrates JPA and Hibernate3 for persistence, the EJB Timer Service and Quartz for lightweight asychronicity, jBPM for workflow, JBoss Rules for business rules, Meldware Mail for email, Hibernate Search and Lucene for full text search, JMS for messaging and JBoss Cache for page fragment caching. Seam layers an innovative rule-based security framework over JAAS and JBoss Rules.

              Seam为持久性整合了JPA,Hibernate3,为轻量级的异步性整合了EJB Timer服务和Quartz, 为工作流整合了          jBPM, 为业务规则整合了JBoss Rules, 为电子邮件整合了 Meldware Mail, 为全文搜索整和Hibernate Search
    和Lucene, 为消息服务整合了JMS,为页面片段缓存整合了JBoss Cache. Seam甚至在JAAS和JBoss Rules上增加了创新的基于规则的安全性框架。

    There's even JSF tag libraries for rendering PDF, outgoing email, charts and wikitext. Seam components may be called synchronously as a Web Service, asynchronously from client-side JavaScript or Google Web Toolkit or, of course, directly from JSF.

              甚至,Seam提供了JSF标签库来绘制PDF, 电子邮件,图表和Wiki文本。Seam组件能够被Web Service同步          调用,被客户端JavaScript或者Google Web Toolkit异步调用,当然也能被JSF直接调用。

    Get started now! 现在就开始!

    Seam works in any Java EE application server, and even works in Tomcat. If your environment supports EJB 3.0, great! If it doesn't, no problem, you can use Seam's built-in transaction management with JPA or Hibernate3 for persistence. Or, you can deploy JBoss Embedded in Tomcat, and get full support for EJB 3.0.

              Seam可以在任何Java EE应用服务器中运行,甚至可以在Tomcat中运行。如果你的环境支持EJB3.0,那很          好!不过如果不支持,那也没关系。你可以使用Seam内置的事务管理,使用JPA或Hibernate3实现持久性。          或者,你可以将JBoss Embedded部署到Tomcat中,从而获得对EJB3.0的完全支持。

    It turns out that the combination of Seam, JSF and EJB3 is the simplest way to write a complex web application in Java. You won't believe how little code is required!

    现实表明,将Seam,JSF, EJB3结合以来使用是开发复杂的Java Web应用的最简单的方式!你根本不能相信它需要的代码量少到什么地步!



    Prev Up Next
    Seam - Contextual Components Home Chapter 1. Seam Tutorial