天天看点

SEAM学习(五)---Seam 组件

Seam 组件

Seam 组件是POJO(Plain Old Java Objects)。特别地,他们是JavaBean或者EJB 3.0 enterprise bean。Seam并不强求组件是EJB,甚至可以不使用EJB 3.0兼容的容器,Seam在设计的时候处处考虑对EJB 3.0的支持,并且包含对EJB 3.0的深度整合。

*

EJB 3.0 stateless Session Beans

*

EJB 3.0 stateful Session Beans

*

EJB 3.0 entity beans

*

JavaBeans

*

EJB 3.0 message-driven beans

(1)无状态Session Bean

无状态Session Bean组件无法在多次调用之间保持状态。因此,它们通常在不同的Seam上下文中,操作其他组件的状态。他们可以作为JSF的action listener,但是不能为JSF组件的显示提供属性。

因为每次请求都产生一个新的实例,无状态session bean可以并发访问。把其实例和请求相关联是EJB3容器的责任(通常这些实例会从一个可重用的池中分配,所以你可能会发现某些实例变量还保存着上次使用的痕迹。)

无状态Session Bean总是生活在无状态上下文中。

无状态Session Bean是Seam组件中最没趣的了。

Seam无状态Session Bean组件可以使用 Component.getInstance() 或者 @In(create=true) 实例化。它们不能直接使用JNDI或者 new 操作实例化。

(2)有状态Session Bean

有状态Session Bean不仅可以在bean的多次调用之间保持状态,而且在多次请求之间也可以保持状态。不由数据库保存的状态通常应该由有状态Session Bean保持。这是Seam和其他web框架之间的一个显著的不同点。其他框架把当前会话的信息直接保存在 HttpSession 中,而在Seam中你应该把它们保存在有状态Session Bean的实例中,该实例被绑定到会话上下文。这可以让Seam来替你管理状态的生命周期,并且保证在多个不同的并发会话中没有状态冲突。

有状态Session Bean经常被作为JSF action listener使用,也可以作为JSF显示或者form提交的backing bean(支持bean ,或称后台bean),提供属性供组件访问。

默认情况下,有状态Session Bean会被绑定到Conversation Context。它们绝不会绑定到page或stateless context。

对Session范围的有状态Session Bean的并发请求,会被Seam按顺序串行处理。

Seam有状态Session Bean组件可以使用 Component.getInstance() 或者 @In(create=true) 实例化。它们不能直接使用JNDI或者 new 操作实例化。

(3) 实体Bean

实体Bean可以被绑定到上下文变量,起到Seam组件的作用。因为Entity除了上下文标识之外,还有持久标识,Entity实体通常明确的由Java Code绑定,而非由Seam隐性初始化。

Entity Bean实体不支持双向注入或者上下文划分。对Entity Bean的调用也不会触发验证。

Entity Bean通常不作为JSF的action listener使用,但经常作为JSF组件用于显示或者form提交的后台bean,提供属性功能。特别是,当Entity作为后台Bean的时候,它会和一个无状态Session Bean扮演的action listener联用,来实现CRUD之类的功能。

默认情况下,Entity Bean被绑定到Conversation Context。他们永远不能被绑定到无状态Context。

注意,在集群环境中,把Entity Bean直接绑定到Conversation或者Session范围的Seam上下文变量,与在有状态Session Bean中保持一个对Entity Bean的引用相比,性能比较差。因此,并非所有的Seam应用程序都会把Entity Bean定义为Seam组件。

Seam实体Bean组件可以使用 Component.getInstance()、@In(create=true) 或者直接使用 new 操作来实例化。

(4) JavaBeans

JavaBeans可以像无状态或者有状态Session Bean那样使用。但是,它们不能提供Session Bean那么多的功能(声明式事务划分、声明式安全性、高效的集群状态复制、EJB 3.0持久化、超时方法等等)。

在后面有一章,我们会展示如何在没有EJB容器的情况下使用Seam和Hibernate。此时,组件是JavaBeans,而非Session Beans。 但是注意,在很多应用服务器中,对Conversation或Session 范围的Seam JavaBean组件集群操作,要比对有状态Session Bean组件集群慢。

默认,JavaBeans是绑定到Event Context的。

对Session范围的JavaBeans的并发请求总是会被Seam转化为串行执行。

Seam JavaBean组件可以使用 Component.getInstance()、@In(create=true) 或者直接使用 new 操作来实例化。

(5)消息驱动Bean

消息驱动Bean通常作为Seam组件。但是,消息驱动Bean与其他Seam组件的调用方式非常不同——它们并非通过Context变量调用,它们会监听发送到JMS Queue或者Topic的消息。

消息驱动Bean不能被绑定到Seam上下文。它们也不能访问它们的“调用者”的Session或者会话状态。但是,它们支持双向注入和一些其他的Seam功能。

消息驱动Bean不会被应用实例化,它是在接受到一条消息时由EJB容器来完成实例化的。

(6) 拦截

为了表演Seam的魔术(双向注入,上下文划分,校验等),它必须对组件调用进行拦截。对JavaBean而言,Seam可以完全控制组件的初始化,不需要特别的配置。对于Entity Bean,也不需要拦截器,因为双向注入和上下文划分不起作用。 对Session Bean,我们必须为它注册EJB拦截器。我们可以使用注解,比如:

@Stateless

@Interceptors(SeamInterceptor.class)

public class LoginAction implements Login {

...

}

但是更好的办法是在 ejb-jar.xml 中定义拦截器。

<interceptors>

<interceptor>

<interceptor-class>org.jboss.seam.ejb.SeamInterceptor</interceptor-class>

</interceptor>

</interceptors>

<assembly-descriptor>

<interceptor-binding>

<ejb-name>*</ejb-name>

<interceptor-class>org.jboss.seam.ejb.SeamInterceptor</interceptor-class>

</interceptor-binding>

</assembly-descriptor>

(7) 组件名字

所有Seam组件都需要名字。我们可以通过 @Name 注解来命名组件:

@Name("loginAction")

@Stateless

public class LoginAction implements Login {

...

}

这个名字是 seam component name,和EJB规范定义的任何其他名字都没有关系。 但是,Seam组件名字就相当于JSF管理的Bean Name的名字,因此,可以理解为这两个概念是等同的。

@Name 不是定义组件名称的唯一方式,但是我们总得要在 某个地方 来指定名字。 否则,Seam 所有的注解部分就无法工作。

就如同在JSF中,Seam组件实例绑定成上下文变量时,其名字通常和组件名相同。 因此,例如我们可以通过 Contexts.getStatelessContext().get("loginAction") 来访问 LoginAction。 特别是,不管Seam自己何时初始化一个组件,它将这个新实例以组件的名字绑定成一个变量。 但是,又和JSF一样,应用程序也可以把组件绑定成其他的上下文变量,只需通过API编程调用。 例如,当前登录的用户(User)可以被绑定成为Session上下文中的 currentUser 变量,而同时,另一个用作某种管理功能的用户则被绑定成对话上下文的 user 变量。

对非常大型的应用程序,经常使用全限定名;内置的Seam组件就是这样。

@Name("com.jboss.myapp.loginAction")

@Stateless

@Interceptors(SeamInterceptor.class)

public class LoginAction implements Login {

...

}

我们可以在Java代码和JSF表达式语言中使用全限定的组件名称。

<h:commandButton type="submit" value="Login"

action="#{com.jboss.myapp.loginAction.login}"/>

这很啰嗦,Seam也提供了把全限定名简写的办法。在 components.xml 文件中加入类似这样的一行:

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

所有的Seam内置组件都有全限定名,但大多数都在Seam jar文件的 components.xml 中简写为简单的名字。

(8) 定义组件范围(Defining the Component Scope)

我们可以使用 @Scope 注解来覆盖默认的组件范围(上下文)。这可以让我们定义组件实例被Seam初始化后绑定到的具体上下文。

@Name("user")

@Entity

@Scope(SESSION)

public class User {

...

}

org.jboss.seam.ScopeType定义了可能范围的枚举.

(9)具有多个角色的组件(Components with multiple roles)

有些Seam组件类可以在系统中具有多个角色。例如,我们经常有一个 User 类用作Session-Scoped组件,代表当前用户,同时它又在用户管理界面中被用作Conversation-Scoped组件。@Role 注解让我们可以定义组件在另一个范围中的额外角色名 —— 这可以让我们把相同的组件类绑定成不同的上下文变量。(任何Seam组件 实例 都可以被绑定到多个上下文变量,但@Role使得我们也可以在类的级别做到这一点,从而享受自动实例化的优点。)

@Name("user")

@Entity

@Scope(CONVERSATION)

@Role(name="currentUser", scope=SESSION)

public class User {

...

}

@Roles 注解可以让我们为组件指定任意多的附加角色。

@Name("user")

@Entity

@Scope(CONVERSATION)

@Roles({@Role(name="currentUser", scope=SESSION),

@Role(name="tempUser", scope=EVENT)})

public class User {

...

}

(10) 内置组件

和很多优秀的框架一样,Seam自产自用,主要实现了一系列的内置Seam拦截器(后文详述)和Seam组件。这让应用程序在运行时和内置的组件交互变得很容易,甚至可以用自己编写的实现来替换掉内置组件,由此来定制Seam的基本功能。内置组件在 org.jboss.seam.core 这个Seam 命名空间中定义,Java包名也是相同的。

像所有Seam组件一样,内置组件也可以被注射,但是它们也提供了便利的instance()静态方法:

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