(今天是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第十一章的内容。
欢迎赐教。