Seam 入门
上一篇 / 下一篇 2008-04-19 00:19:58 / 个人分类:Seam
本教程假定你已下载JBoss AS 4.0.5并安装了EJB 3.0 profile(请使用JBoss AS安装器)。你也得下载一份Seam并解压到工作目录上。
各示例的目录结构仿效以下形式:
网页、图片及样式表可在examples/registration/view目录中找到。
诸如部署描述文件及数据导入脚本之类的资源可在目录examples/registration/resources中找到。
Java源代码保存在examples/registration/src中。
Ant构建脚本放在examples/registration/build.xml文件中。
第一步,确保已安装Ant,并正确设定了$ANT_HOME及$JAVA_HOME的环境变量。接着在Seam的根目录下的build.properties文件中正确设定JBoss AS 4.0.5的安装路径。 若一切就绪,就可在JBoss的安装根目录下敲入bin/run.sh或bin/run.bat命令来启动JBoss AS。(译注:此外,请安装JDK1.5以上以便能直接运行示例代码)
现在只要在Seam安装目录examples/registration下输入ant deploy就可构建和部署示例了。
试着在浏览器中访问此链接:http://localhost:8080/seam-registration/。
首先,确保已安装Ant,并正确设定了$ANT_HOME及$JAVA_HOME的环境变量。接着在Seam的根目录下的build.properties文件中正确设定Tomcat 6.0的安装路径。你需要按照25.5.1章节“安装嵌入式的Jboss”中的指导配置 (当然, SEAM也可以脱离Jboss在TOMCAT上直接运行)。
至此,就可在Seam安装目录examples/registration中输入ant deploy.tomcat构建和部署示例了。
最后启动Tomcat。
试着在浏览器中访问此链接:http://localhost:8080/jboss-seam-registration/。
当你部署示例到Tomcat时,任何的EJB3组件将在JBoss的可嵌入式的容器,也就是完全独立的EJB3容器环境中运行。
注册示例是个极其普通的应用,它可让新用户在数据库中保存自己的用户名,真实的姓名及密码。 此示例并不想一下子就把Seam的所有的酷功能全部秀出。然而, 它演示了EJB3 会话Bean作为JSF动作监听器及Seam的基本配置的使用方法。
或许你对EJB 3.0还不太熟悉,因此我们会对示例的慢慢深入说明。
此示例的首页显示了一个非常简单的表单,它有三个输入字段。试着在表单上填写内容并提交,一旦输入数据被提交后就会在数据库中保存一个user对象。

本示例由两个JSP页面,一个实体Bean及无状态的会话Bean来实现。

让我们看一下代码,就从最“底层”的实体Bean开始吧。
我们需要EJB 实体Bean来保存用户数据。这个类通过注解声明性地定义了persistence及validation属性。它也需要一些额外的注解来将这个类定义为Seam的组件。
Example 1.1.
@Entity@Name("user")
@Scope(SESSION)
@Table(name="users")
public class User implements Serializable { private static final long serialVersionUID = 1881413500711441951L; private String username;
private String password; private String name; public User(String name, String password, String username) { this.name = name; this.password = password; this.username = username; } public User() {}
@NotNull @Length(min=5, max=15)
public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } @NotNull public String getName() { return name; } public void setName(String name) { this.name = name; } @Id @NotNull @Length(min=5, max=15)
public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } }
![]() | EJB3标准注解@Entity表明了User类是个实体Bean. |
![]() | Seam组件需要一个组件名称,此名称由注解@Name来指定。此名称必须在Seam应用内唯一。当JSF用一个与组件同名的名称去请求Seam来解析上下文变量, 且该上下文变量尚未定义(null)时,Seam就将实例化那个组件,并将新实例绑定给上下文变量。 在此例中,Seam将在JSF第一次遇到名为user的变量时实例化User。 |
![]() | 每当Seam实例化一个组件时,它就将始化后的实例绑定给组件中默认上下文的上下文变量。默认的上下文由@Scope注解指定。UserBean是个会话作用域的组件。 |
![]() | EJB标准注解@Table表明了将User类映射到users表上。 |
![]() | name、password及username都是实体Bean的持久化属性。所有的持久化属性都定义了访问方法。当JSF渲染输出及更新模型值阶段时需要调用该组件的这些方法。 |
![]() | EJB和Seam都要求有空的构造器。 |
![]() | @NotNull和@Length注解是Hibernate Validator框架的组成部份, Seam集成了Hibernate Validator并让你用它来作为数据校验(尽管你可能并不使用Hibernate作为持久化层)。 |
![]() | 标准EJB注解@Id表明了实体Bean的主键属性。 |
这个例子中最值得注意的是@Name和@Scope注解,它们确立了这个类是Seam的组件。
接下来我们将看到User类字段在更新模型值阶段时直接被绑定给JSF组件并由JSF操作, 在此并不需要冗余的胶水代码来在JSP页面与实体Bean域模型间来回拷贝数据。
然而,实体Bean不应该进行事务管理或数据库访问。故此,我们无法将此组件作为JSF动作监听器,因而需要会话Bean。
在Seam应用中大都采用会话Bean来作为JSF动作监听器(当然我们也可选择JavaBean)。
在我们的应用程序中确实存在一个JSF动作和一个会话Bean方法。在此示例中,只有一个JSF动作,并且我们使用会话Bean方法与之相关联并使用无状态Bean,这是由于所有与动作相关的状态都保存在UserBean中。
这是示例中比较有趣的代码部份:
Example 1.2.
@Stateless@Name("register") public class RegisterAction implements Register { @In
private User user; @PersistenceContext
private EntityManager em; @Logger
private Log log; public String register()
{ List existing = em.createQuery( "select username from User where username=#{user.username}")
.getResultList(); if (existing.size()==0) { em.persist(user); log.info("Registered new user #{user.username}");
return "/registered.jsp";
} else { FacesMessages.instance().add("User #{user.username} already exists");
return null; } } }
![]() | EJB标准注解@Stateless将这个类标记为无状态的会话Bean。 |
![]() | 注解@In将Bean的一个属性标记为由Seam来注入。 在此例中,此属性由名为user的上下文变量注入(实例的变量名)。 |
![]() | EJB标准注解@PersistenceContext用来注入EJB实体管理器。 |
![]() | Seam的@Logger注解用来注入组件的Log实例。 |
![]() | 动作监听器方法使用标准的EJB3EntityManagerAPI来与数据库交互,并返回JSF的输出结果。 请注意,由于这是个会话Bean,因此当register()方法被调用时事务就会自动开始,并在结束时提交(commit)。 |
![]() | 请注意Seam让你在EJB-QL中使用JSF EL表达式。因此可在标准JPAQuery对象上调用普通的JPAsetParameter()方法,这样岂不妙哉? |
![]() | LogAPI为显示模板化的日志消息提供了便利。 |
![]() | 多个JSF动作监听器方法返回一个字符串值的输出,它决定了接下来应显示的页面内容。 空输出(或返回值为空的动作监听器方法)重新显示上一页的内容。 在普通的JSF中,用JSF的导航规则(navigation rule)来决定输出结果的JSF视图id是很常用的。 这种间接性对于复杂的应用是非常有用的,值得去实践。但是,对于象示例这样简单的的应用,Seam让你使用JSF视图id作为输出结果,以减少对导航规则的需求。请注意,当你用视图id作为输出结果时,Seam总会执行一次浏览器的重定向。 |
![]() | Seam提供了大量的内置组件(built-in components)来协助解决那些经常遇到的问题。 用FacesMessages组件就可很容易地来显示模板化的错误或成功的消息。 内置的Seam组件还可由注入或通过调用instance()方法来获取。 |
这次我们并没有显式指定@Scope,若没有显式指定时,每个Seam 组件类型就使用其默认的作用域。对于无状态的会话Bean, 其默认的作用域就是无状态的上下文。实际上所有的无状态的会话Bean都属于无状态的上下文。
会话Bean的动作监听器在此小应用中履行了业务和持久化逻辑。在更复杂的应用中,我们可能要将代码分层并重构持久化逻辑层成 专用数据存取组件,这很容易做到。但请注意Sean并不强制你在应用分层时使用某种特定的分层策略。
此外,也请注意我们的SessionBean会同步访问与web请求相关联的上下文(比如在User对象中的表单的值),状态会被保持在事务型的资源里(EntityManager 对象)。 这是对传统J2EE的体系结构的突破。再次说明,如果你习惯于传统J2EE的分层,也可以在你的Seam应用实行。但是对于许多的应用,这是明显的没有必要 。
很自然,我们的会话Bean需要一个本地接口。
所有的Java代码就这些了,现在去看一下部署描述文件。
如果你此前曾接触过许多的Java框架,你就会习惯于将所有的组件类放在某种XML文件中来声明,那些文件就会随着项目的不断成熟而不断加大到最终到不可收拾的地步。 对于Seam应用,你尽可放心,因为它并不要求应用组件都要有相应的XML。大部份的Seam应用要求非常少量的XML即可,且XML文件大小不会随着项目的增大而快速增长。
无论如何,若能为某些组件(特别是Seam内置组件)提供某些外部配置往往是有用的。这样一来,我们就有几个选择, 但最灵活的选择还是使用位于WEB-INF目录下的components.xml配置文件。 我们将用components.xml文件来演示Seam怎样在JNDI中找到EJB组件:
Example 1.4.
<components xmlns="http://jboss.com/products/seam/components"
xmlns:core="http://jboss.com/products/seam/core">
<core:init jndi-pattern="@jndiPattern@"/>
</components>此代码配置了Seam内置组件org.jboss.seam.core.init的jndiPattern属性。这里需要奇怪的@符号是因为ANT脚本会在部署应用时将正确的JNDI语法在标记处自动填补
我们将以WAR的形式来部署此小应用的表示层,因此需要web部署描述文件。
Example 1.5.
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<!-- Seam -->
<listener>
<listener-class>org.jboss.seam.servlet.SeamListener</listener-class>
</listener>
<!-- MyFaces -->
<listener>
<listener-class>
org.apache.myfaces.webapp.StartupServletContextListener
</listener-class>
</listener>
<context-param>
<param-name>javax.faces.STATE_SAVING_METHOD</param-name>
<param-value>client</param-value>
</context-param>
<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<!-- Faces Servlet Mapping -->
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>*.seam</url-pattern>
</servlet-mapping>
</web-app>此web.xml文件配置了Seam和JSF。所有Seam应用中的配置与此处的配置基本相同。
绝大多数的Seam应用将JSF来作为表示层。因而我们通常需要faces-config.xml。SEAM将用Facelet定义视图表现层,所以我们需要告诉JSF用Facelet作为它的模板引擎。
Example 1.6.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE faces-config
PUBLIC "-//Sun Microsystems, Inc.//DTD JavaServer Faces Config 1.0//EN"
"http://java.sun.com/dtd/web-facesconfig_1_0.dtd">
<faces-config>
<!-- A phase listener is needed by all Seam applications -->
<lifecycle>
<phase-listener>org.jboss.seam.jsf.SeamPhaseListener</phase-listener>
</lifecycle>
</faces-config>注意我们不需要申明任何JSF managed Bean!因为我们所有的managed Bean都是通过经过注释的Seam组件。所以在Seam的应用中,faces-config.xml比原始的JSF更少用到。
实际上,一旦你把所有的基本描述文件配置完毕,你所需写的唯一类型的XML文件就是导航规则及可能的jBPM流程定义。对于Seam而言,流程(process flow)及配置数据是唯一真正属于需要XML定义的。
在此简单的示例中,因为我们将视图页面的ID嵌入到Action代码中,所以我们甚至都不需要定义导航规则。
ejb-jar.xml文件将SeamInterceptor绑定到压缩包中所有的会话Bean上,以此实现了Seam与EJB3的整合。
<ejb-jar xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/ejb-jar_3_0.xsd"
version="3.0">
<interceptors>
<interceptor>
<interceptor-class>org.jboss.seam.ejb.SeamInterceptor</interceptor-class>
</interceptor>
</interceptors>
<assembly-descriptor>
<interceptor-binding>
<ejb-name>*</ejb-name>
<interceptor-class>org.jboss.seam.ejb.SeamInterceptor</interceptor-class>
</interceptor-binding>
</assembly-descriptor>
</ejb-jar>persistence.xml文件告诉EJB的持久化层在哪找到数据源,该文件也含有一些厂商特定的设定。此例在程序启动时自动创建数据库Schema。
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
version="1.0">
<persistence-unit name="userDatabase">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<jta-data-source>java:/DefaultDS</jta-data-source>
<properties>
<property name="hibernate.hbm2ddl.auto" value="create-drop"/>
</properties>
</persistence-unit>
</persistence>对于Seam应用的视图可由任意支持JSF的技术来实现。在此例中,我们使用了JSP,因为大多数的开发人员都很熟悉, 且这里并没有其它太多的要求。(我们建议你在实际开发中使用Facelets)。
Example 1.7.
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
<%@ taglib uri="http://jboss.com/products/seam/taglib" prefix="s" %>
<html>
<head>
<title>Register New User</title>
</head>
<body>
<f:view>
<h:form>
<table border="0">
<s:validateAll>
<tr>
<td>Username</td>
<td><h:inputText value="#{user.username}"/></td>
</tr>
<tr>
<td>Real Name</td>
<td><h:inputText value="#{user.name}"/></td>
</tr>
<tr>
<td>Password</td>
<td><h:inputSecret value="#{user.password}"/></td>
</tr>
</s:validateAll>
</table>
<h:messages/>
<h:commandButton type="submit" value="Register" action="#{register.register}"/>
</h:form>
</f:view>
</body>
</html>这里的<s:validateAll>标签是Seam特有的。 该JSF组件告诉JSF让它用实体Bean中所指定的Hibernat验证器注解来验证所有包含输入的字段。
Example 1.8.
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
<html>
<head>
<title>Successfully Registered New User</title>
</head>
<body>
<f:view>
Welcome, <h:outputText value="#{user.name}"/>,
you are successfully registered as <h:outputText value="#{user.username}"/>.
</f:view>
</body>
</html>这是个极其普通的使用JSF组件的JSP页面,与Seam毫无相干。
最后,因为我们的应用是要部署成EAR的,因此我们也需要部署描述文件。
Example 1.9.
<?xml version="1.0" encoding="UTF-8"?>
<application xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/application_5.xsd"
version="5">
<display-name>Seam Registration</display-name>
<module>
<web>
<web-uri>jboss-seam-registration.war</web-uri>
<context-root>/seam-registration</context-root>
</web>
</module>
<module>
<ejb>jboss-seam-registration.jar</ejb>
</module>
<module>
<java>jboss-seam.jar</java>
</module>
<module>
<java>el-api.jar</java>
</module>
<module>
<java>el-ri.jar</java>
</module>
</application>此部署描述文件联接了EAR中的所有模块,并把Web应用绑定到此应用的首页/seam-registration。
至此,我们了解了整个应用中所有的部署描述文件!
当提交表单时,JSF请求Seam来解析名为user的变量。由于还没有值绑定到user上(在任意的Seam上下文中), Seam就会实例化user组件,接着把它保存在Seam会话上下文后,然后将User实体Bean实例返回给JSF。
表单输入的值将由在User实体中所指定的Hibernate验证器来验证。 若有非法输入,JSF就重新显示当前页面。否则,JSF就将输入值绑定到User实体Bean的字段上。
接着,JSF请求Seam来解析变量register。 Seam在无状态上下文中找到RegisterAction无状态的会话Bean并把它返回。JSF随之调用register()动作监听器方法。
Seam拦截方法调用并在继续调用之前从Seam会话上下文注入User实体。
register()方法检查所输入用户名的用户是否已存在。 若存在该用户名,则错误消息进入facesmessages组件队列,返回无效结果并触发浏览器重显页面。facesmessages组件嵌在消息字符串的JSF表达式,并将JSFfacesmessage添加到视图中。
若输入的用户不存在,"/registered.jsp"输出就会将浏览器重定向到registered.jsp页。 当JSF来渲染页面时,它请求Seam来解析名为user的变量,并使用从Seam会话作用域返回的User实体的属性值。
在几乎所有的在线应用中都免不了将搜索结果显示成可点击的列表。 因此Sean在JSF层之上提供了特殊的功能,使得我们很容易用EJB-QL或HQL来查询数据并用JSF<h:dataTable>将查询结果显示成可点击的列表。我们将在接下的例子中演示这一功能。

此消息示例中有一个实体Bean,Message,一个会话BeanMessageListBean及一个JSP页面。
Message实体定义了消息的title,text,date和time以及该消息是否已读的标志:
Example 1.10.
@Entity
@Name("message")
@Scope(EVENT)
public class Message implements Serializable
{
private Long id;
private String title;
private String text;
private boolean read;
private Date datetime;
@Id @GeneratedValue
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
@NotNull @Length(max=100)
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
@NotNull @Lob
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
@NotNull
public boolean isRead() {
return read;
}
public void setRead(boolean read) {
this.read = read;
}
@NotNull
@Basic @Temporal(TemporalType.TIMESTAMP)
public Date getDatetime() {
return datetime;
}
public void setDatetime(Date datetime) {
this.datetime = datetime;
}
}如此前的例子,会话BeanMessageManagerBean用来给表单中的两个按钮定义个动作监听器方法, 其中的一个按钮用来从列表中选择消息,并显示该消息。而另一个按钮则用来删除一条消息,除此之外,就没什么特别之处了。
在用户第一次浏览消息页面时,MessageManagerBean会话Bean也负责抓取消息列表,考虑到用户可能以多种方式来浏览该页面,他们也有可能不是由JSF动作来完成,比如用户可能将该页加入收藏夹。 因此抓取消息列表发生在Seam的工厂方法中,而不是在动作监听器方法中。
之所以将此会话Bean设为有状态的,是因为我们想在不同的服务器请求间缓存此消息列表。
Example 1.11.
@Stateful
@Scope(SESSION)
@Name("messageManager")
public class MessageManagerBean implements Serializable, MessageManager
{
@DataModel
private List<Message> messageList;
@DataModelSelection
@Out(required=false)
private Message message;
@PersistenceContext(type=EXTENDED)
private EntityManager em;
@Factory("messageList")
public void findMessages()
{
messageList = em.createQuery("from Message msg order by msg.datetime desc").getResultList();
}
public void select()
{
message.setRead(true);
}
public void delete()
{
messageList.remove(message);
em.remove(message);
message=null;
}
@Remove @Destroy
public void destroy() {}
}![]() | 注解@DataModel暴露了java.util.List类型的属性给JSF页面来作为javax.faces.model.DataModel的实例。 这允许我们在JSF<h:dataTable>的每一行中能使用可点击列表。在此例中,DataModel可在变量名为messageList的会话上下文中被使用。 |
![]() | @DataModelSelection注解告诉了Seam来注入List元素到相应的被点击链接。 |
![]() | 注解@Out直接暴露了被选中的值给页面。 这样一来,每次可点击列表一旦被选中,Message就被会注入给有状态Bean的属性,紧接着向外注入(outjected)给变量名为message的事件上下文的属性。 |
![]() | 此有状态Bean有个EJB3的扩展持久化上下文(extended persistence context)。只要Bean存在,查询中获取的消息就会保留在受管理的状态中。 这样一来,此后对有状态Bean的所有方法调用勿需显式调用EntityManager就可更新这些消息了。 |
![]() | 当我们第一次浏览JSP页面时,messageList上下文变量尚未被初始化,@Factory注解告诉Seam来创建MessageManagerBean的实例并调用findMessages()方法来初始化上下文变量。 我们把findMessages()当作messages的工厂方法。 |
![]() | select()将选中的Message标为已读,并同时更新数据库。 |
![]() | delete()动作监听器方法将选中的Message从数据库中删除。 |
![]() | 对于每个有状态的会话Bean,Seam组件的所有方法中必须有一不带参数的方法被标为@Remove @Destroy以确保在Seam的上下文结束时删除有状态Bean,并同时清除所有服务器端的状态。 |
请注意,这是个会话作用域的Seam组件。它与用户登入会话相关联,并且登入会话的所有请求共享同一个组件的实例。 (在Seam的应用中,我们通常使用会话作用域的组件。)
当然,每个会话Bean都有个业务接口。
@Local
public interface MessageManager
{
public void findMessages();
public void select();
public void delete();
public void destroy();
}从现在起,我们在示例代码中将不再对本地接口作特别的说明。
由于XML文件与此前的示例几乎都一样,因此我们略过了components.xml、persistence.xml、web.xml、ejb-jar.xml、faces-config.xml及application.xml的细节,直接来看一下JSP。
JSP页面就是直接使用JSF<h:dataTable>的组件,并没有与Seam有什么关系。
Example 1.12.
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
<html>
<head>
<title>Messages</title>
</head>
<body>
<f:view>
<h:form>
<h2>Message List</h2>
<h:outputText value="No messages to display" rendered="#{messageList.rowCount==0}"/>
<h:dataTable var="msg" value="#{messageList}" rendered="#{messageList.rowCount>0}">
<h:column>
<f:facet name="header">
<h:outputText value="Read"/>
</f:facet>
<h:selectBooleanCheckbox value="#{msg.read}" disabled="true"/>
</h:column>
<h:column>
<f:facet name="header">
<h:outputText value="Title"/>
</f:facet>
<h:commandLink value="#{msg.title}" action="#{messageManager.select}"/>
</h:column>
<h:column>
<f:facet name="header">
<h:outputText value="Date/Time"/>
</f:facet>
<h:outputText value="#{msg.datetime}">
<f:convertDateTime type="both" dateStyle="medium" timeStyle="short"/>
</h:outputText>
</h:column>
<h:column>
<h:commandButton value="Delete" action="#{messageManager.delete}"/>
</h:column>
</h:dataTable>
<h3><h:outputText value="#{message.title}"/></h3>
<div><h:outputText value="#{message.text2}"/></div>
</h:form>
</f:view>
</body>
</html>当我们首次浏览messages.jsp页面时,无论是否由回传(postback)的JSF(页面请求)或浏览器直接的GET请求(非页面请求),此JSP页面将设法解析messagelist上下文变量。 由于上下文变量尚未被初始化,因此Seam将调用工厂方法findmessages(),该方法执行了一次数据库查询并导致DataModel被向外注入。DataModel提供了渲染<h:dataTable>所需的行数据。
当用户点击<h:commandLink>时,JSF就调用Select()动作监听器。 Seam拦截此调用并将所选行的数据注入给messageManager组件的message属性。 而动作监听器将所选定的Message标为已读。在此调用结束时,Seam向外注入所选定的Message给名为message的变量。 接着,EJB容器提交事务,将Message的已读标记写入数据库。 最后,该网页重新渲染,再次显示消息列表,并在列表下方显示所选消息的内容。
如果用户点击了<h:commandButton>,JSF就调用delete()动作监听器。 Seam拦截此调用并将所选行的数据注入给messageManager组件的message属性。 触发动作监听器,将选定的Message从列表中删除并同时在EntityManager中调用remove()方法。在此调用的最后,Seam刷新messageList上下文变量并清除名为message的上下文变量。 接着,EJB容器提交事务,将Message从数据库中删除。最后,该网页重新渲染,再次显示消息列表。
jBPM提供了先进的工作流程和任务管理的功能。为了体验一下jBPM是如何与Seam集成在一起工作的,在此将给你一个简单的管理“待办事项列表”的应用。由于管理任务列表等功能是jBPM的核心功能,所以在此例中只用了很少的Java代码。

这个例子的核心是jBPM的流程定义(process definition)。此外,还有两个JSP页面和两个简单的JavaBeans(由于他们不用访问数据库,或有其它事务相关的行为,因此并没有用会话Bean)。让我们先从流程定义开始:
Example 1.13.
<process-definition name="todo"> <start-state name="start"><transition to="todo"/> </start-state> <task-node name="todo">
<task name="todo" description="#{todoList.description}">
<assignment actor-id="#{actor.id}"/>
</task> <transition to="done"/> </task-node> <end-state name="done"/>
</process-definition>
![]() | 节点<start-state>代表流程的逻辑开始。一旦流程开始时,它就立即转入todo节点。 |
![]() | <task-node>节点代表等待状态,就是在执行业务流程暂停时,等待一个或多个未完成的任务。 |
![]() | <task>元素定义了用户需要完成的任务。 由于在这个节点只有定义了一个任务,当它完成,或恢复执行时我们就转入结束状态。 此任务从Seam中名为todolist的组件(JavaBeans之一)获得任务description。 |
![]() | 任务在创建时就会被分配给一个用户或一组用户时。在此示例中,任务是分配给当前用户,该用户从一个内置的名为actor的Seam组件中获得。任何Seam组件都可用来执行任务指派。 |
![]() | <end-state>节点定义业务流程的逻辑结束。当执行到达这个节点时,流程实例就要被销毁。 |
如果我们用jBossIDE所提供的流程定义编辑器来查看此流程定义,那它就会是这样:

这个文档将我们的业务流程定义成节点图。这可能是最常见的业务流程:只有一个任务被执行,当这项任务完成之后,业务流程就结束了。
第一个JavaBean处理登入界面login.jsp。 它的工作就是用actor组件初始化jBPM用户id(在实际的应用中,它也需要验证用户。)
Example 1.14.
@Name("login")
public class Login {
@In
private Actor actor;
private String user;
public String getUser() {
return user;
}
public void setUser(String user) {
this.user = user;
}
public String login()
{
actor.setId(user);
return "/todo.jsp";
}
}在此我们使用了@In来将actor属性值注入到Seam内置的Actor组件。
JSP页面本身并没有什么特别之处:
Example 1.15.
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h"%>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f"%>
<html>
<head>
<title>Login</title>
</head>
<body>
<h1>Login</h1>
<f:view>
<h:form>
<div>
<h:inputText value="#{login.user}"/>
<h:commandButton value="Login" action="#{login.login}"/>
</div>
</h:form>
</f:view>
</body>
</html>第二个JavaBean负责启动业务流程实例及结束任务。
Example 1.16.
@Name("todoList")
public class TodoList {
private String description;
public String getDescription()
{
return description;
}
public void setDescription(String description) {
this.description = description;
}
@CreateProcess(definition="todo")
public void createTodo() {}
@StartTask @EndTask
public void done() {}
}![]() | description属性从JSP页接受用户输入,并将它暴露给流程定义,这样就可让Seam来设定任务的descrption。 |
![]() | Seam的@CreateProcess注解为指定名称的流程定义创建了一个新的jBPM流程实例。 |
![]() | Seam的@StartTask注解用来启动任务,@EndTask用来结束任务,并允许恢复执行业务流程。 |
在实际的应用中,@StartTask及@EndTask不会出现在同一个方法中,因为为了完成任务,通常用应用中有许多工作要做。
最后,该应用的主要内容在todo.jsp中:
Example 1.17.
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
<%@ taglib uri="http://jboss.com/products/seam/taglib" prefix="s" %>
<html>
<head>
<title>Todo List</title>
</head>
<body>
<h1>Todo List</h1>
<f:view>
<h:form. id="list">
<div>
<h:outputText value="There are no todo items." rendered="#{empty taskInstanceList}"/>
<h:dataTable value="#{taskInstanceList}" var="task" rendered="#{not empty taskInstanceList}">
<h:column>
<f:facet name="header">
<h:outputText value="Description"/>
</f:facet>
<h:inputText value="#{task.description}"/>
</h:column>
<h:column>
<f:facet name="header">
<h:outputText value="Created"/>
</f:facet>
<h:outputText value="#{task.taskMgmtInstance.processInstance.start}">
<f:convertDateTime type="date"/>
</h:outputText>
</h:column>
<h:column>
<f:facet name="header">
<h:outputText value="Priority"/>
</f:facet>
<h:inputText value="#{task.priority}" style="width: 30"/>
</h:column>
<h:column>
<f:facet name="header">
<h:outputText value="Due Date"/>
</f:facet>
<h:inputText value="#{task.dueDate}" style="width: 100">
<f:convertDateTime type="date" dateStyle="short"/>
</h:inputText>
</h:column>
<h:column>
<s:button value="Done" action="#{todoList.done}" taskInstance="#{task}"/>
</h:column>
</h:dataTable>
</div>
<div>
<h:messages/>
</div>
<div>
<h:commandButton value="Update Items" action="update"/>
</div>
</h:form>
<h:form. id="new">
<div>
<h:inputText value="#{todoList.description}"/>
<h:commandButton value="Create New Item" action="#{todoList.createTodo}"/>
</div>
</h:form>
</f:view>
</body>
</html>让我们对此逐一加以说明。
该JSP页面将从Seam内置组件taskInstanceList获得的任务渲染成任务列表,此列表在JSF表单内被定义。
<h:form. id="list">
<div>
<h:outputText value="There are no todo items." rendered="#{empty taskInstanceList}"/>
<h:dataTable value="#{taskInstanceList}" var="task" rendered="#{not empty taskInstanceList}">
...
</h:dataTable>
</div>
</h:form>列表中的每个元素就是一个jBPM类taskinstance的实例。 以下代码简单地展示了列表中每一任务的有趣特性。为了让用户能更改description、priority及due date的值,我们使用了输入控件。
<h:column>
<f:facet name="header">
<h:outputText value="Description"/>
</f:facet>
<h:inputText value="#{task.description}"/>
</h:column>
<h:column>
<f:facet name="header">
<h:outputText value="Created"/>
</f:facet>
<h:outputText value="#{task.taskMgmtInstance.processInstance.start}">
<f:convertDateTime type="date"/>
</h:outputText>
</h:column>
<h:column>
<f:facet name="header">
<h:outputText value="Priority"/>
</f:facet>
<h:inputText value="#{task.priority}" style="width: 30"/>
</h:column>
<h:column>
<f:facet name="header">
<h:outputText value="Due Date"/>
</f:facet>
<h:inputText value="#{task.dueDate}" style="width: 100">
<f:convertDateTime type="date" dateStyle="short"/>
</h:inputText>
</h:column>该按钮通过调用被注解为@StartTask @EndTask的动作方法来结束任务。它把任务id作为请求参数传给Seam:
<h:column>
<s:button value="Done" action="#{todoList.done}" taskInstance="#{task}"/>
</h:column>(请注意,这是在使用Seamseam-ui.jar包中的JSF<s:button>控件。)
这个按钮是用来更新任务属性。当提交表单时,Seam和jBPM将直接更改任务的持久化,不需要任何的动作监听器方法:
<h:commandButton value="Update Items" action="update"/>
第二个表单通过调用注解为@CreateProcess的动作方法来创建新的项目(item)。
<h:form. id="new">
<div>
<h:inputText value="#{todoList.description}"/>
<h:commandButton value="Create New Item" action="#{todoList.createTodo}"/>
</div>
</h:form>这个例子还需要另外几个文件,但它们只是标准的jBPM和Seam配置并不是很有趣。
清空Cookie - 联系我们 - ITPUB个人空间 - 交流论坛 - 空间列表 - 站点存档 - 升级自己的空间
Powered by X-Space
3.0.2
© 2001-2007 Comsenz Inc.
京ICP证:010037号



