alartin's profileWindows Live 共享空间PhotosBlogListsMore ![]() | Help |
|
|
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页面导航配置的三要素:
但是你可能立刻就能发现这种处理方式的弊病来:
这种方式的页面导航被称为无状态的页面导航。 需要强调的是:对于这种无状态的页面导航方式,除了JSF导航(使用navigation.xml)外,Seam还支持一种自己实现的无状态页面导航(使用pages.xml或者某个页面的xxx.page.xml): <page view-id="/admin/admin.xhtml"> 这里Seam也使用了类似JSF的页面导航三要素:
如果,在你的Seam应用程序中你准备使用无状态的页面导航的话,你有两种选择:
我们将在后面的文章中比较这两种选择。 Seam不仅仅只支持这种无状态的页面导航!更棒的是,Seam支持有状态的页面导航! 提醒:在Seam中,Seam实现的页面导航被成为页面流Pageflow。
在我们讲解有状态的页面导航之前,我们先回顾一下无状态页面导航的弊病包括:虽然页面配置简单,但是代码中要控制页面导航规则;无法很容易的看清楚导航规则,因为约束小。 Seam通过使用JBPM来帮助我们支持创建有状态的页面导航! 需要注意的是:Seam在两种层次上使用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)来配置的,几个要素:
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输入的值绑定一样。
关键的思想是我们如何从另一个页面到/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页面时,页面参数的传递就有必要了。你可以通过页面参数的传递来记住先前你在哪个视图上 (例如你在保存页面上,必须记住上个页面你正在编辑的实体)
听起来太复杂了,这有必要么?当你用它的时候你就知道了。这值得下功夫去理解。页面参数是在非Faces请求间传递状态的最佳方式。如果你想让你的搜索结果页面也能做书签的话,我们就能够在同一个代码中使用它来处理POST和GET两种请求了。页面参数能够减少视图定义中请求参数列表的重复性,并且让在代码中处理重定向更加简单。 对话和验证 导向 在Seam中,你可以使用JSF的faces-config.xml文件来设置标准的JSF向导规则。但是JSF的向导规则有很多缺点:
还有一个问题就是如果在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提供了内置的上下文事件,我们可以为每个事件定制自己的监听器: 关于验证的事件
关于变量的事件
关于上下文的事件
关于对话的事件
关于页面流的事件
关于流程和任务的事件
关于组件的事件
关于JSF生命周期的事件
关于认证和安全的事件
关于例外的事件
关于事务的事件
JBoss Seam的事件机制(1):概述JBoss Seam如何做到松耦合的架构呢?光有具备上下文的组件模型是不够的。还必须有:
JBoss Seam的组件模型本身就是为事件驱动的应用来设计的。这些事件都是通过JSF的表达式语言的方法绑定来映射的。在JBoss Seam中,事件可以分为:
<h:commandButton value="Click me!" action="#{helloWorld.sayHello}"/> JSF的按钮绑定动作
<start-page name="hello" view-id="/hello.jsp">
<transition to="hello">
<action expression="#{helloWorld.sayHello}"/>
</transition>
</start-page> jBPM流程或页面流定义
<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><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);
}
}
Seam组件可以监听(观察)这些事件,就像他们观察组件驱动的事件一样。我们将在别的文章中专门讲述。 August 29 JBoss Seam: 具有上下文的组件模型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上下文包括:
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也可以配置为在客户端浏览器中保留对话状态。 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会话。 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)来管理和持久化。业务流程跨越多个用户的多个交互,所以这个状态以一种很好的方式在多用户间共享。当前任务决定当前业务流程实例,业务流程的生命周期通过外部的业务定义语言来定义,所以无需为业务流程的划分指定特别的注释。 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将自己的配置信息和元模型存储在应用上下文中。 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. 通常,我们通过注入方式从上下文中获得组件,通过注出方式将组件实例放入上下文。 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: 某些时候,例如上面所说的,组件从某个特定的已经知道的范围中获得。但是有些时候,所有有状态的范围都要通过优先队列搜索,优先队列如下:
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页面中通过名字来访问组件,一个优先搜索都会发生。 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客户端可以安全的使用可变的会话和对话状态,不用增加开发者的任何工作。 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支持下面的组件类型:
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. 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. 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. 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. 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. 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 But a much better way is to define the interceptor in ejb-jar.xml. 然而一个更好的方式是在ejb-jar.xml中定义拦截器。 <interceptors> All seam components need a name. We can assign a name to a component using the @Name annotation: 所有Seam组件都要有名称,我们通过@Name注释给组件命名: @Name("loginAction")
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")
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" 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文件中。 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")
org.jboss.seam.ScopeType defines an enumeration of possible scopes. 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")
The @Roles annotation lets us specify as many additional roles as we like. @Roles注释能让我们指定多个角色 @Name("user")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}!");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引入了注入的泛化概念双向注射。对比注入,双向注射是:
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")
or into a setter method: 或者注入到一个setter方法中: @Name("loginAction")
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")
(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")
or from a getter method: @Name("loginAction")
An attribute may be both injected and outjected: @Name("loginAction")
or: @Name("loginAction")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={....}). 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:
@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):
Suppose we have a component named messageSender that talks to a JMS queue. @Name("messageSender")
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")
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. Who is not totally fed up with seeing noisy code like this? private static final Log log = LogFactory.getLog(CreateOrderAction.class); 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; 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; 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. 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")
Or, you can use the @ReadOnly annotation to achieve a similar effect: @Name("account")
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 Note that the EntityHome class in the Seam Application Framework provides a great example of managing an entity bean instance using a Seam component. 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) The second style is a method of type void which binds the value to the context variable itself: @DataModel List<Customer> 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")
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")
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. August 28 JBoss Seam: IntroductionSeam is an application framework for Enterprise Java. It is inspired by the following principles: Seam是一种企业级Java的应用框架。它的灵感来源于以下原理或技术:
![]() 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应用的最简单的方式!你根本不能相信它需要的代码量少到什么地步!
|
|
|