Chapter 11. Seam 应用程序框架
(今天是2007年国庆日,能在今天加班搞农业普查数据处理,并抽闲翻译这样一篇seam文章,真的是特别特别有意义噢。)
通过写带标注的普通java类,seam使创建应用变得非常容易。这样的类不需要扩展任何特殊的接口或超类。通过提供一系列内置组件,我们可以进一步简化一些通用的编程工作。对这些内置组件的使用是通过设置xomponents.xml(适用于简单情况)或其他的设置。
在一个应用中,如果需要使用Hibernate 或 JPA对数据库进行基本操作,Seam 应用程序框架可以减少编码数量。
需要强调的是,这个框架极其简单,仅仅少量的简单易懂的、易于扩展的类。“魔法”就在seam本身-即使你创建seam应用时没有使用这个框架,你也在使用同样的魔法。
11.1. 前言
Seam 应用程序框架提供的组件可以用以下两种方式之一使用:
第一种,在components.xml安装、设置组件实例,就像我们使用其他类型seam组件。例如,以下components.
xml代码片段安装一个组件,可以对Person entity进行基本操作:
如果这样看起来太像“在xml中编程”,亦可以这样使用扩展方式:
第二种方式有较大优势:可以方便地加入额外函数,并覆盖内置函数(为便于扩展和定制,框架类已被仔细设计)。
第二个优势是,你的类可以是EJB stateful sessin beans(并非必须如此,只要你愿意,也可以是plain JavaBean components)。
截至目前,seam应用程序框架提供了四种内置组件:EntityHome 、HibernateEntityHome
for CRUD、EntityQuery 、 HibernateEntityQuery for queries。
Home 与 Query组件可以用在这些工作期中:session, event 或conversation。使用何种工作期取决于你的应用程序的状态模型。
seam应用程序框架仅适用Seam-managed persistence contexts。默认情况,组件将查找名称为entityManager的persistence context。
11.2. Home objects
一个Home object提供了对一个特定entity class的持久化操作(persistence operations)。假设有这样一个Person类:
我们可以定义一个personHome 组件,以设置的方式:
或者以扩展的方式:
一个Home object提供了以下操作:persist(), remove(), update() and getInstance()。在调用remove(), 或 update()操作之前,必须首先用setId()设置对象的标志符。
可以在JSF页面中使用Home对象,例如:
通常,如果在引用Person的时候,直接使用person就太好了,因此,我们通过在components.xml中加入一行来做到这点(如果我们使用设置的方式):
或者可以通过加入@Factory方式到PersonHome(如果我们使用扩展的方式):
以上改变简化了JSF页面:
好了,我们新建一个Person entries。是的,这就是所需的全部代码!现在,如果我们想要显示、更新、删除数据库中的Person 记录,则需要把记录的id传给PersonHome。做到这点,页参数是一个好办法:
现在可以向JSF页面加入更多操作:
如果到该页面的连接不带任何请求参数,会显示"Create Person"页面。
如果针对personId 提供一个请求参数,会显示"Edit Person"页面。假设我们需要建立一条Person记录,包含初始化的国籍信息。非常简单,通过设置的方式:
或者通过扩展的方式:
当然,Country 可能是一个由其他Home object管理的对象,例如,CountryHome。为了增加更多复杂操作(例如关联管理,等等),我们可以对PersonHome增加方法(methods)。
如果一个操作成功完成,Home对象自动显示faces messages。为定制这些信息,我们再一次通过设置的方式:
或者使用扩展的方式:
不过定制信息的最好方式是把它们放到一个seam找得到的资源包中(这个包的默认名称是messages)。
11.3. Query objects
如果需要数据库中Person 记录列表,可以用Query 对象,例如:
可以在JSF页面这样用它:
如果需要分页:
我们需要也各页面参数来确定显示哪一页:
JSF分页控制代码有点繁琐,但是比较易于控制:
真正的搜索页面允许使用者输入一系列选择性的搜索条件,缩小返回的搜索结果数目。
Query对象允许你指定可选的“限制条件”来支持这种重要功能。
注意一个"example" 对象的使用。
后台记录改变后,为刷新查询结果,我们观察
org.jboss.seam.afterTransactionSuccess 事件:
或者,当person记录通过PersonHome保存、更新、删除后,为马上刷新查询:
以上例子显示了通过设置的方式达到复用。不过,对Query 对象来说,通过扩展的方式达到复用也是同样可以的。
11.4. Controller objects
在Seam应用框架中完全可选的部分是Controller 类和它的子类Entity-Controller HibernateEntityController 和 BusinessProcessController。它们提供了操作常用的内置组件及其方式的捷径。它们可以节省击键次数(自动添加字符),对新手学习seam的内在功能有很大的推动作用。
例如,Seam registration例子中的RegisterAction看上去像这样:
如你所见,应用它们后并没有带来明显的改善... ...
以上是翻译Seam Reference Documentation 2.0.0 CR1第十一章的内容。
欢迎赐教。
通过写带标注的普通java类,seam使创建应用变得非常容易。这样的类不需要扩展任何特殊的接口或超类。通过提供一系列内置组件,我们可以进一步简化一些通用的编程工作。对这些内置组件的使用是通过设置xomponents.xml(适用于简单情况)或其他的设置。
在一个应用中,如果需要使用Hibernate 或 JPA对数据库进行基本操作,Seam 应用程序框架可以减少编码数量。
需要强调的是,这个框架极其简单,仅仅少量的简单易懂的、易于扩展的类。“魔法”就在seam本身-即使你创建seam应用时没有使用这个框架,你也在使用同样的魔法。
11.1. 前言
Seam 应用程序框架提供的组件可以用以下两种方式之一使用:
第一种,在components.xml安装、设置组件实例,就像我们使用其他类型seam组件。例如,以下components.
xml代码片段安装一个组件,可以对Person entity进行基本操作:
<framework:entity-home name="personHome"
entity-class="eg.Person"
entity-manager="#{personDatabase}">
<framework:id>#{param.personId}</framework:id>
</framework:entity-home>
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; }
}
@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; }
}
第二种方式有较大优势:可以方便地加入额外函数,并覆盖内置函数(为便于扩展和定制,框架类已被仔细设计)。
第二个优势是,你的类可以是EJB stateful sessin beans(并非必须如此,只要你愿意,也可以是plain JavaBean components)。
截至目前,seam应用程序框架提供了四种内置组件:EntityHome 、HibernateEntityHome
for CRUD、EntityQuery 、 HibernateEntityQuery for queries。
Home 与 Query组件可以用在这些工作期中:session, event 或conversation。使用何种工作期取决于你的应用程序的状态模型。
seam应用程序框架仅适用Seam-managed persistence contexts。默认情况,组件将查找名称为entityManager的persistence context。
11.2. Home objects
一个Home object提供了对一个特定entity class的持久化操作(persistence operations)。假设有这样一个Person类:
@Entity
public class Person {
@Id private Long id;
private String firstName;
private String lastName;
private Country nationality;
//getters and setters...
}
public class Person {
@Id private Long id;
private String firstName;
private String lastName;
private Country nationality;
//getters and setters...
}
我们可以定义一个personHome 组件,以设置的方式:
<framework:entity-home name="personHome" entity-class="eg.Person" />
或者以扩展的方式:
@Name("personHome")
public class PersonHome extends EntityHome<Person> {}
public class PersonHome extends EntityHome<Person> {}
一个Home object提供了以下操作:persist(), remove(), update() and getInstance()。在调用remove(), 或 update()操作之前,必须首先用setId()设置对象的标志符。
可以在JSF页面中使用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>
<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的时候,直接使用person就太好了,因此,我们通过在components.xml中加入一行来做到这点(如果我们使用设置的方式):
<factory name="person" value="#{personHome.instance}"/>
<framework:entity-home name="personHome" entity-class="eg.Person" />
<framework:entity-home name="personHome" entity-class="eg.Person" />
或者可以通过加入@Factory方式到PersonHome(如果我们使用扩展的方式):
@Name("personHome")
public class PersonHome extends EntityHome<Person> {
@Factory("person")
public Person initPerson() { return getInstance(); }
}
public class PersonHome extends EntityHome<Person> {
@Factory("person")
public Person initPerson() { return getInstance(); }
}
以上改变简化了JSF页面:
<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>
<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 entries。是的,这就是所需的全部代码!现在,如果我们想要显示、更新、删除数据库中的Person 记录,则需要把记录的id传给PersonHome。做到这点,页参数是一个好办法:
<pages>
<page view-id="/editPerson.jsp">
<param name="personId" value="#{personHome.id}"/>
</page>
</pages>
<page view-id="/editPerson.jsp">
<param name="personId" value="#{personHome.id}"/>
</page>
</pages>
现在可以向JSF页面加入更多操作:
<h1>
<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>
<h:commandButton value="Create Person"
action="#{personHome.persist}"
rendered="#{!personHome.persist}"/>
<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>
<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>
<h:commandButton value="Create Person"
action="#{personHome.persist}"
rendered="#{!personHome.persist}"/>
<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>
如果到该页面的连接不带任何请求参数,会显示"Create Person"页面。
如果针对personId 提供一个请求参数,会显示"Edit Person"页面。假设我们需要建立一条Person记录,包含初始化的国籍信息。非常简单,通过设置的方式:
<factory name="person"value="#{personHome.instance}"/>
<framework:entity-home name="personHome"
entity-class="eg.Person"
new-instance="#{newPerson}"/>
<component name="newPerson"
class="eg.Person">
<property name="nationality">#{country}</property>
</component>
<framework:entity-home name="personHome"
entity-class="eg.Person"
new-instance="#{newPerson}"/>
<component name="newPerson"
class="eg.Person">
<property name="nationality">#{country}</property>
</component>
或者通过扩展的方式:
@Name("personHome")
public class PersonHome extends EntityHome<Person> {
@In Country country;
@Factory("person")
public Person initPerson() { return getInstance(); }
protected Person createInstance() {
return new Person(country);
}
}
The Seam Application Framework
public class PersonHome extends EntityHome<Person> {
@In Country country;
@Factory("person")
public Person initPerson() { return getInstance(); }
protected Person createInstance() {
return new Person(country);
}
}
The Seam Application Framework
当然,Country 可能是一个由其他Home object管理的对象,例如,CountryHome。为了增加更多复杂操作(例如关联管理,等等),我们可以对PersonHome增加方法(methods)。
@Name("personHome")
public class PersonHome extends EntityHome<Person> {
@In Country country;
@Factory("person")
public Person initPerson() { return getInstance(); }
protected Person createInstance() {
return new Person(country);
}
public void migrate()
{
getInstance().setCountry(country);
update();
}
}
public class PersonHome extends EntityHome<Person> {
@In Country country;
@Factory("person")
public Person initPerson() { return getInstance(); }
protected Person createInstance() {
return new Person(country);
}
public void migrate()
{
getInstance().setCountry(country);
update();
}
}
如果一个操作成功完成,Home对象自动显示faces messages。为定制这些信息,我们再一次通过设置的方式:
<factory name="person"
value="#{personHome.instance}"/>
<framework:entity-home name="personHome"
entity-class="eg.Person"
new-instance="#{newPerson}">
<framework:created-message>
New person #{person.firstName} #{person.lastName} created
</framework:created-message>
<framework:deleted-message>
Person #{person.firstName} #{person.lastName} deleted
</framework:deleted-message>
<framework:updated-message>
Person #{person.firstName} #{person.lastName} updated
</framework:updated-message>
</framework:entity-home>
<component name="newPerson"
class="eg.Person">
<property name="nationality">#{country}</property>
</component>
value="#{personHome.instance}"/>
<framework:entity-home name="personHome"
entity-class="eg.Person"
new-instance="#{newPerson}">
<framework:created-message>
New person #{person.firstName} #{person.lastName} created
</framework:created-message>
<framework:deleted-message>
Person #{person.firstName} #{person.lastName} deleted
</framework:deleted-message>
<framework:updated-message>
Person #{person.firstName} #{person.lastName} updated
</framework:updated-message>
</framework:entity-home>
<component name="newPerson"
class="eg.Person">
<property name="nationality">#{country}</property>
</component>
或者使用扩展的方式:
@Name("personHome")
public class PersonHome extends EntityHome<Person> {
@In Country country;
@Factory("person")
public Person initPerson() { return getInstance(); }
protected Person createInstance() {
return new Person(country);
}
protected String getCreatedMessage()
{
return "New person #{person.firstName} #{person.lastName} created";
}
protected String getUpdatedMessage()
{
return "Person #{person.firstName} #{person.lastName} updated";
}
protected String getDeletedMessage()
{
return "Person #{person.firstName} #{person.lastName} deleted";
}
}
public class PersonHome extends EntityHome<Person> {
@In Country country;
@Factory("person")
public Person initPerson() { return getInstance(); }
protected Person createInstance() {
return new Person(country);
}
protected String getCreatedMessage()
{
return "New person #{person.firstName} #{person.lastName} created";
}
protected String getUpdatedMessage()
{
return "Person #{person.firstName} #{person.lastName} updated";
}
protected String getDeletedMessage()
{
return "Person #{person.firstName} #{person.lastName} deleted";
}
}
不过定制信息的最好方式是把它们放到一个seam找得到的资源包中(这个包的默认名称是messages)。
Person_created=New person #{person.firstName} #{person.lastName} created
Person_deleted=Person #{person.firstName} #{person.lastName} deleted
Person_updated=Person #{person.firstName} #{person.lastName} updated
Person_deleted=Person #{person.firstName} #{person.lastName} deleted
Person_updated=Person #{person.firstName} #{person.lastName} updated
这使国际化成为可能,并且使你的代码和设置与表现信息分离。
11.3. Query objects
如果需要数据库中Person 记录列表,可以用Query 对象,例如:
<framework:entity-query name="people"
ejbql="select p from Person p"/>
ejbql="select p from Person p"/>
可以在JSF页面这样用它:
<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>
<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"/>
ejbql="select p from Person p"
order="lastName"
max-results="20"/>
我们需要也各页面参数来确定显示哪一页:
<pages>
<page view-id="/searchPerson.jsp">
<param name="firstResult" value="#{people.firstResult}"/>
</page>
</pages>
<page view-id="/searchPerson.jsp">
<param name="firstResult" value="#{people.firstResult}"/>
</page>
</pages>
JSF分页控制代码有点繁琐,但是比较易于控制:
<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>
<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>
真正的搜索页面允许使用者输入一系列选择性的搜索条件,缩小返回的搜索结果数目。
Query对象允许你指定可选的“限制条件”来支持这种重要功能。
<component name="examplePerson" class="Person"/>
<framework:entity-query name="people"
ejbql="select p from Person p"
order="lastName"
max-results="20">
<framework:restrictions>
<value>lower(firstName) like lower( concat(#{examplePerson.firstName},'%') )</value>
<value>lower(lastName) like lower( concat(#{examplePerson.lastName},'%') )</value>
</framework:restrictions>
</framework:entity-query>
<framework:entity-query name="people"
ejbql="select p from Person p"
order="lastName"
max-results="20">
<framework:restrictions>
<value>lower(firstName) like lower( concat(#{examplePerson.firstName},'%') )</value>
<value>lower(lastName) like lower( concat(#{examplePerson.lastName},'%') )</value>
</framework:restrictions>
</framework:entity-query>
注意一个"example" 对象的使用。
<h1>Search for people</h1>
<h:form>
<div>First name: <h:inputText value="#{examplePerson.firstName}"/></div>
<div>Last name: <h:inputText value="#{examplePerson.lastName}"/></div>
<div><h:commandButton value="Search" action="/search.jsp"/></div>
</h:form>
<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>
<h:form>
<div>First name: <h:inputText value="#{examplePerson.firstName}"/></div>
<div>Last name: <h:inputText value="#{examplePerson.lastName}"/></div>
<div><h:commandButton value="Search" action="/search.jsp"/></div>
</h:form>
<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>
后台记录改变后,为刷新查询结果,我们观察
org.jboss.seam.afterTransactionSuccess 事件:
<event type="org.jboss.seam.afterTransactionSuccess">
<action execute="#{people.refresh}" />
</event>
<action execute="#{people.refresh}" />
</event>
或者,当person记录通过PersonHome保存、更新、删除后,为马上刷新查询:
<event type="org.jboss.seam.afterTransactionSuccess.Person">
<action execute="#{people.refresh}" />
</event>
<action execute="#{people.refresh}" />
</event>
以上例子显示了通过设置的方式达到复用。不过,对Query 对象来说,通过扩展的方式达到复用也是同样可以的。
11.4. Controller objects
在Seam应用框架中完全可选的部分是Controller 类和它的子类Entity-Controller HibernateEntityController 和 BusinessProcessController。它们提供了操作常用的内置组件及其方式的捷径。它们可以节省击键次数(自动添加字符),对新手学习seam的内在功能有很大的推动作用。
例如,Seam registration例子中的RegisterAction看上去像这样:
@Stateless
@Name("register")
public class RegisterAction extends EntityController implements Register
{
@In private User user;
public String register()
{
List existing = createQuery("select u.username from User u where u.username=:username")
.setParameter("username", user.getUsername())
.getResultList();
if ( existing.size()==0 )
{
persist(user);
info("Registered new user #{user.username}");
return "/registered.jspx";
}
else
{
addFacesMessage("User #{user.username} already exists");
return null;
}
}
}
@Name("register")
public class RegisterAction extends EntityController implements Register
{
@In private User user;
public String register()
{
List existing = createQuery("select u.username from User u where u.username=:username")
.setParameter("username", user.getUsername())
.getResultList();
if ( existing.size()==0 )
{
persist(user);
info("Registered new user #{user.username}");
return "/registered.jspx";
}
else
{
addFacesMessage("User #{user.username} already exists");
return null;
}
}
}
如你所见,应用它们后并没有带来明显的改善... ...
以上是翻译Seam Reference Documentation 2.0.0 CR1第十一章的内容。
欢迎赐教。