SeamFramework.orgCommunity Documentation

Web Beans: Java 上下文和依赖注入

关于依赖注入和上下文状态管理的Java新规范


注释
I. 使用具备上下文的对象
1. Web Beans起步
1.1. 你的第一个Web Bean
1.2. 什么是Web Bean?
1.2.1. API类型,绑定类型和依赖注入
1.2.2. 部署类型
1.2.3. 范围
1.2.4. Web Bean的名字和统一表达式语言
1.2.5. 拦截器绑定类型
1.3. 什么样的对象能够称为Web Bean?
1.3.1. 简单的Web Bean
1.3.2. 企业级Web Bean
1.3.3. 生产者方法
1.3.4. JMS端点
2. JSF Web应用例子
3. 使用Web Beans的参考实现
3.1. 使用JBoss AS 5
3.2. 使用Apache Tomcat 6.0
3.3. 使用Glassfish
3.4. 猜数字例子
3.4.1. 猜数字例子
3.5. 翻译器例子
4. 依赖注入
4.1. 绑定注释
4.1.1. 成员绑定注释
4.1.2. 绑定注释的组合
4.1.3. 绑定注释和生产者方法
4.1.4. 默认的绑定类型
4.2. 部署类型
4.2.1. 激活部署类型
4.2.2. 部署类型优先级
4.2.3. 部署类型样例
4.3. 修正没有满足条件的依赖
4.4. 客户代理
4.5. 通过编程查找获得一个Web Bean
4.6. 生命周期回调方法,@Resource@EJB@PersistenceContext
4.7. InjectionPoint 对象
5. 范围和上下文
5.1. 范围类型
5.2. 内置范围
5.3. 对话范围
5.3.1. 对话划分
5.3.2. 对话的传播
5.3.3. 对话超时
5.4. 依赖的伪范围
5.4.1. @New注释
6. 生产者方法
6.1. 生产者方法的范围
6.2. 注入到生产者方法中
6.3. 在生产者方法中使用 @New
II. 开发松耦合的代码
7. 拦截器
7.1. 拦截器绑定
7.2. 实现拦截器
7.3. 激活拦截器
7.4. 成员拦截器绑定
7.5. 多个拦截器绑定注释
7.6. 拦截器绑定类型的继承
7.7. @Interceptors 的使用
8. 装饰器
8.1. 委托属性
8.2. 激活装饰器
9. 事件
9.1. 事件观察者
9.2. 事件生产者
9.3. 动态注册观察者
9.4. 事件绑定成员
9.5. 多个事件绑定
9.6. 事务性的观察者
III. 最大程度地使用强类型
10. 模板
10.1. 一个模板默认的范围和部署类型
10.2. 通过模板来限制范围和类型
10.3. 模板的拦截器绑定
10.4. 模板的默认命名
10.5. 标准的模板
11. 特化
11.1. 使用特化
11.2. 特化的优点
12. 使用XML定义Web Bean
12.1. 声明Web Bean类
12.2. 声明Web Bean的元数据
12.3. 声明Web Bean成员
12.4. 声明内联的Web Beans
12.5. 使用一个模式
IV. Web Beans和Java EE生态系统
13. Java EE整合
13.1. 将Java EE资源注入到一个Web Bean中
13.2. 从Servlet调用一个Web Bean
13.3. 从消息驱动Bean中调用一个Web Bean
13.4. JMS端点
13.5. 打包和部署
14. 扩展Web Bean
14.1. Manager 对象
14.2. Bean
14.3. Context 接口
15. 下一步
V. Web Beans Reference
16. Application Servers and environments supported by Web Beans
16.1. Using Web Beans with JBoss AS
16.2. Glassfish
16.3. Tomcat (or any plain Servlet container)
16.4. Java SE
16.4.1. Web Beans SE Module
17. JSR-299 extensions available as part of Web Beans
17.1. Web Beans Logger
17.2. XSD Generator for JSR-299 XML deployment descriptors
A. 将Web Bean参考实现整合到其他环境中
A.1. Web Bean的参考实现SPI
A.1.1. Web Bean的发现
A.1.2. EJB services
A.1.3. JPA services
A.1.4. 事务服务
A.1.5. 应用上下文
A.1.6. 自举和停止
A.1.7. JNDI
A.1.8. 资源加载
A.1.9. Servlet 注入
A.2. 容器的合约

JSR-299最近将名字从"Web Beans"改为"Java上下文和依赖注入"。参考指南仍然使用"Web Beans"名称,参考实现仍然使用"Web Beans RI"名称。其他的文档,博客,论坛帖子等等可能使用新的命名,包括新的JSR-299参考实现的名字- "Web Beans"。

你也将发现一些指定的最近的一些功能缺失了(例如生产者域,实现(realization),异步事件,Java EE资源的XML映射)

The Web Beans (JSR-299)规范为Java EE环境定义了一套服务以便开发者能够更轻松地开发应用。Web Beans在现有的包括JavaBeans和企业JavaBeans在内的Java组件类型之上增强了生命周期和交互的模型层。作为传统的Java EE编程模型的补充,Web Beans服务提供了:

依赖注入和具备上下文的生命周期管理节省了开发者出于对接口的不熟悉而不得不处理下列问题所浪费时间:

一个Web Bean仅仅需要指定它所依赖的其他Web Bean的类型和语义。它不需要知道他所依赖的任何Web Bean的实际生命周期,具体的实现,线程模型或者这个Web Bean的其他客户端。更棒的是,它所依赖的Web Bean的具体实现和生命周期,线程模型可以根据部署场景而更改,却丝毫不影响任何客户端。

事件,拦截器和装饰器增强了这个模型固有的松耦合特性:

最重要的是,Web Beans以一种类型安全的方式提供所有的特性。Web Beans从来不使用基于字符串的标识符来决定交互的对象如何关联在一起。至于XML, 虽然它仍旧是一个选项,但也可以几乎不使用。取而代之,Web Bean使用Java对象模型的类型信息和一种新的绑定注释模式将Web Beans和它们的依赖以及拦截器,装饰器和事件消费者关联在一起。

Web Beans服务是相当普遍的,可以应用在下列Java EE环境中的组件类型中:

Web Beans甚至提供了必要的整合点,以便未来的Java EE规范或者其他非标准的框架中的不同组件能够和Web Beans轻松整合,使用Web Beans服务以及和任何其他类型的Web Bean交互。

Web Beans受到了包括Seam, Guice和Spring在内的很多现有的Java框架的影响。然而,Web Beans具有自己鲜明的特性:在类型安全方面优于Seam,比Spring更加具备状态和使用更少的XML, 比Guice更加具备Web和企业应用的能力。

最重要的是,Web Beans是一个JCP标准,能够干净地整合到Java EE中。Web Beans也可以整合到任何使用轻量的内嵌EJB的Java SE环境中。

你是否已经迫不及待想要开始编写你的第一个Web Bean了?或许,你仍旧抱有怀疑态度,想要知道Web Beans规范会给出什么样的圈套让你跳。好消息是你也许已经编写并且使用了好几百个或者好几千个Web Bean了。你也许甚至想不起来你写的第一个Web Bean了。

除非特殊情况,每个具有一个非参构造器的Java类都可以是一个Web Bean。这包括了每个JavaBean, 并且每个EJB3的会话Bean都是一个Web Bean。当然,你每天已经写过的JavaBean和EJB无法使用Web Beans规范定义的新服务,但是你能够通过Web Beans的XML配置将这些组件配置为Web Bean,然后将其注入到其他Web Bean中。你甚至可以不用修改已有代码就可以为其添加拦截器和装饰器。

假定我们有两个已经写好的Java类,我们已经在不同的应用中使用它们好多年了。第一个类将一个字符串解析为一个句子列表:

public class SentenceParser {

    public List<String
> parse(String text) { ... }
}

第二个已有类是一个无状态的会话Bean,这个会话Bean作为一个外部系统的前端,能够将句子从一种语言翻译到另一个语言:

@Stateless

public class SentenceTranslator implements Translator {
    public String translate(String sentence) { ... }
}

Translator是本地接口:

@Local

public interface Translator {
    public String translate(String sentence);
}

不幸的是,我们没有Java类能够翻译整个文档。因此,让我们写一个Web Bean来做这个工作:

public class TextTranslator {

    
    private SentenceParser sentenceParser;
    private Translator sentenceTranslator;
    
    @Initializer
    TextTranslator(SentenceParser sentenceParser, Translator sentenceTranslator) {
        this.sentenceParser = sentenceParser;
        this.sentenceTranslator = sentenceTranslator;
    }
    
    public String translate(String text) {
        StringBuilder sb = new StringBuilder();
        for (String sentence: sentenceParser.parse(text)) {
            sb.append(sentenceTranslator.translate(sentence));
        }
        return sb.toString();
    }
    
}

通过将其注入到一个Web Bean,Servlet或者EJB,我们能够获得一个 TextTranslator的实例:

@Initializer

public setTextTranslator(TextTranslator textTranslator) {
    this.textTranslator = textTranslator;
}

或者,我们可以直接调用Web Bean管理器的方法获得这个实例:

TextTranslator tt = manager.getInstanceByType(TextTranslator.class);

但是,等一下:TextTranslator并没有一个无参构造器!它仍然是一个Web Bean么?好吧,一个没有无参构造器的类依然能够成为一个Web Bean,只要你在它的一个构造器上使用@Initializer注释即可。

就像你猜到的一样, @Initializer注释和依赖注入有关! @Initializer可以应用到一个Web Bean的构造器或者方法上,它告诉Web Bean管理器在初始化一个Web Bean的时候去调用这个构造器或者方法。Web Beam管理器能够将其他的Web Bean注入到构造器或者方法的参数中。

在系统初始化的时候,Web Bean管理器必须验证只存在一个Web Bean能够满足每个注入点。在我们的例子中,如果没有Translator实现—如果SentenceTranslator EJB没有被部署—Web Bean管理器将会抛出一个UnsatisfiedDependencyException异常。如果多于一个Translator实现,Web Bean管理器将会抛出一个AmbiguousDependencyException异常。

那么, Web Bean究竟是什么?

一个Web Bean是一个包含业务逻辑的应用类。一个Web Bean能够从Java代码中直接调用,也可以通过统一表达语言调用。一个Web Bean可以访问事务性的资源。Web Bean之间的依赖通过Web Bean管理器自动管理。大部分Web Bean是具有 状态上下文的。Web Bean的生命周期总是通过Web Bean管理器进行管理。

让我们后退一步。"上下文"究竟意味着什么?既然Web Beans可以是有状态的,那它关系到我到底拥有哪个Bean实例。和无状态组件模型(例如无状态的会话Bean)或者一个单例模型组件(例如Servlet或者单例Bean)不同,一个Web Bean的不同客户端看到的Web Bean的状态是不同的。客户端所见的状态取决于这个客户端拥有的是哪一个Web Bean实例的引用。

然而,Web Bean像无状态或者单例模型那样,却和有状态的会话Bean不同,客户端无法通过显式地创建或者销毁它来控制实例的生命周期。取而代之,Web Bean的范围决定了:

给定一个Web Bean应用的线程,将可能有一个激活的上下文和Web Bean的范围关联。这个上下文可能对于该线程是唯一的(例如,如果这个Web Bean是请求范围的),或者这个上下文可能被某些其他线程共享(例如,如果这个Web Bean是会话范围的),这个上下文甚至可以被所有线程共享(例如,这个Web Bean是应用范围的)。

在同一个上下文中执行的客户端(例如,其他的Web Bean)看到的是同一个Web Bean的实例。但是不同的上下文中的客户端看到的是不同的实例。

具备上下文的模型带来的一个巨大优点是它允许有状态的Web Bean可以像服务一样使用!客户端不需要关注本身以及它使用的Web Bean的生命周期,甚至它根本不需要知道生命周期是什么。Web Bean通过传递消息来交互,Web Bean的实现定义了他们自己状态的生命周期。Web Bean是松耦合的,因为:

我们能够使用一个实现相同接口,具有不同生命周期(一个不同的范围)的Web Bean替换一个Web Bean而不会影响其他的Web Bean实现。实际上,Web Bean定义了一个复杂的机制能够在部署时覆盖Web Bean的实现,我们将在第 4.2 节 “部署类型”一章阐述。

需要注意的是并非所有的Web Bean的客户端都是Web Bean。其他对象诸如Servlet或者消息驱动Bean#151;天生不可注入的, 具备上下文的对象—也可以通过注入获得Web Bean的引用。

讨论的够多了。我们看些更正式些的,依据规范:

一个Web Bean包括:

  • 一套(非空)API类型

  • 一套(非空)绑定注释类型

  • 一个范围

  • 一个部署类型

  • 可选的,一个Web Bean的名字

  • 一套拦截器绑定类型

  • 一个Web Bean实现

让我们看看这些术语对于Web Bean的开发者都意味着什么。

Web Bean通常通过依赖注入获得其他Web Bean的引用。任何注入的属性都要指定一个"合约",这个合约必须满足注入的Web Bean的要求。这个合约是:

一个API指的是用户定义的类或者接口。(如果Web Bean是一个EJB会话Bean,API类型是 @Local 接口或者Bean类的本地视图)。一个绑定类型表示某个客户端可见的语义,这个语义由API的某个实现而不是其他实现来满足。

绑定类型通过用户定义的注释来表现,这些注释自己需要通过@BindingType来注释。例如,下面的注入点有一个API类型 PaymentProcessor和绑定类型@CreditCard

@CreditCard PaymentProcessor paymentProcessor

如果在一个注入点没有显式的指定一个绑定类型,那么默认的绑定类型是@Current

对于每个注入点,Web Bean管理器都会搜索满足合约的Web Bean(实现了API并且拥有所有的绑定类型),然后将这个Web Bean注入。

下面的Web Bean拥有一个绑定类型@CreditCard,并且实现了API类型PaymentProcessor。因此,这个Web Bean可以被注入到这个例子的注入点中:

@CreditCard

public class CreditCardPaymentProcessor 
    implements PaymentProcessor { ... }

如果一个Web Bean没有显式的指定一套绑定类型,它将只有一个绑定类型:默认的绑定类型@Current

Web Bean定义了一个复杂但是很直观的解析算法来帮助容器确定如何处理多个Web Bean满足特定合约的情况。我们将在 第 4 章 依赖注入一章中详述。

我们已经看到,JavaBean, EJB和其他Java类都可以成为Web Bean。但是,确切地说,什么样的对象能够称为Web Bean?

规范指出所有EJB3类型的会话Bean或者单例Bean都是企业级Web Bean。消息驱动Bean不是Web Beans—因为它们不能被注入到其他对象中#151;但是它们可以使用大部分Web Bean的功能,包括依赖注入和拦截器。

一个企业级Web Bean的本地接口,只要没有一个通配类型参数或者类型变量,它都是这个企业级Web Bean的API类型,该接口的每个父接口也都是这个企业级Web Bean的API类型。如果EJB Bean有一个Bean类的本地视图,这个Bean类和它的每个父类也是一个API类型。

有状态的会话Bean应该声明一个无参的删除方法或者一个拥有@Destructor注释的删除方法。Web Bean管理器将在这个有状态会话Bean实例的生命周期结束时调用这个方法。这个方法被称为企业级Web Bean的销毁方法。

@Stateful @SessionScoped

public class ShoppingCart {
    ...
    
    @Remove
    public void destroy() {}
}

我们什么时候使用企业级Web Bean来替代简单Web Bean呢?当我们需要EJB提供的高级的企业服务的时候,例如:

在这中情况下,我们应该使用一个企业级Web Bean。当我们不需要这些服务的时候,一个简单Web Bean就足够了。

很多Web Bean(包括任何会话范围或者应用范围的Web Bean)都需要并发访问。因此,EJB3.1提供的并发管理相当有用。大部分会话范围和应用范围的Web Bean应该是EJB。

对于拥有重量级资源引用或者大量内部状态的Web Bean来说,它能够从高级的容器管理的生命周期获得好处。这些生命周期由EJB的 @Stateless/@Stateful/@Singleton模型定义,它支持钝化和实例池。

最后,什么情况下应该使用方法水平的事务管理,方法水平的安全,计时器,远程方法或者异步方法应该是显而易见的。

从简单Web Bean起步最容易,然后转换到EJB上,过程很简单,只须添加一个注释:@Stateless, @Stateful 或者 @Singleton

让我们通过一个完整的例子来演示这些想法。我们将是使用JSF来实现一个应用的用户登录/退出功能。首先,我们定一个Web Bean来持有登录过程中用户输入的用户名和密码:

@Named @RequestScoped

public class Credentials {
        
    private String username;
    private String password;
    
    public String getUsername() { return username; }
    public void setUsername(String username) { this.username = username; }
    
    public String getPassword() { return password; }
    public void setPassword(String password) { this.password = password; }
    
}

这个Web Bean和下面的JSF表单的login绑定:

<h:form>

    <h:panelGrid columns="2" rendered="#{!login.loggedIn}">
        <h:outputLabel for="username"
>Username:</h:outputLabel>
        <h:inputText id="username" value="#{credentials.username}"/>
        <h:outputLabel for="password"
>Password:</h:outputLabel>
        <h:inputText id="password" value="#{credentials.password}"/>
    </h:panelGrid>
    <h:commandButton value="Login" action="#{login.login}" rendered="#{!login.loggedIn}"/>
    <h:commandButton value="Logout" acion="#{login.logout}" rendered="#{login.loggedIn}"/>
</h:form
>

实际的工作由一个会话范围的Web Bean完成。这个会话范围的Web Bean维护当前登录用户的信息,并且将User实体暴露给其他Web Bean:

@SessionScoped @Named

public class Login {
    @Current Credentials credentials;
    @PersistenceContext EntityManager userDatabase;
    private User user;
    
    public void login() {
            
        List<User
> results = userDatabase.createQuery(
           "select u from User u where u.username=:username and u.password=:password")
           .setParameter("username", credentials.getUsername())
           .setParameter("password", credentials.getPassword())
           .getResultList();
        
        if ( !results.isEmpty() ) {
           user = results.get(0);
        }
        
    }
    
    public void logout() {
        user = null;
    }
    
    public boolean isLoggedIn() {
       return user!=null;
    }
    
    @Produces @LoggedIn User getCurrentUser() {
        return user;
    }
}

当然, @LoggedIn是一个绑定注释:

@Retention(RUNTIME)

@Target({TYPE, METHOD, FIELD})
@BindingType
public @interface LoggedIn {}

现在,任何其他的Web Bean可以轻松地将当前用户注入:

public class DocumentEditor {


    @Current Document document;
    @LoggedIn User currentUser;
    @PersistenceContext EntityManager docDatabase;
    
    public void save() {
        document.setCreatedBy(currentUser);
        docDatabase.persist(document);
    }
    
}

希望这个例子能够让你尝试了Web Bean的编程模型。在下一章中,我们将更加深入的研究Web Bean的依赖注入。

Web Bean参考实现由Seam项目开发。你可以从the downloads page下载最新的开发者版本。

Web Beans RI自带了两个例子:webbeans-numberguess (一个仅包含一个简单Bean的WAR应用例子)和webbeans-translator (一个包含企业Bean的EAR应用例子)。为了运行例子,你需要:

  • 最新的Web Bean参考实现版本,

  • JBoss AS 5.0.0.GA, 和

  • Apache Tomcat 6.0.X,和

  • Ant 1.7.0.

当前,Web Beans参考实现只能运行在JBoss AS 5之上。你需要从jboss.org下载JBoss AS 5.0.0.GA, 然后解压。例如:

$ cd /Applications
$ unzip ~/jboss-5.0.0.GA.zip

然后从seamframework.org下载Web Beans的参考实现,然后解压。例如:

$ cd ~/
$ unzip ~/webbeans-1.0.0.ALPHA1.zip

然后,我们需要告诉Web Beans JBoss的位置。编辑jboss-as/build.properties,设置jboss.home属性。例如:

jboss.home=/Applications/jboss-5.0.0.GA

为了安装更新,你需要安装Ant 1.7.0,设置ANT_HOME环境变量。例如:

$ unzip apache-ant-1.7.0.zip
$ export ANT_HOME=~/apache-ant-1.7.0

然后,你需要安装更新,更新脚本使用Maven来自动下载Web Beans和EJB3。

$ cd webbeans-1.0.0.ALPHA1/jboss-as
$ ant update

现在,你可以部署你的第一个例子了!

提示

例子的构建脚本包含多个目标:

  • ant restart - 以exploded形式部署例子

  • ant explode - 无需重新部署,更新一个exploded形式部署的例子

  • ant deploy - 以压缩jar包形式部署例子

  • ant undeploy - 将例子从服务器中移除

  • ant clean - 清除例子

部署猜数字(numberguess)例子:

$ cd examples/numberguess
ant deploy

启动 JBoss AS:

jboss.home=/Applications/jboss-5.0.0.GA

提示

如果你使用Windows操作系统,则使用run.bat 脚本。

等待应用部署完毕,好好体验一下http://localhost:8080/webbeans-numberguess

Web Bean参考实现的第二个简单例子能够将你的文本翻译为拉丁文。猜数字例子是一个WAR应用,仅仅使用了一个简单Beans;翻译器例子是一个EAR应用,包含了打包在EJB模块中的企业Beans。试一下:

$ cd examples/traslator
ant deploy

等待应用部署,试一下http://localhost:8080/webbeans-translator

然后从seamframework.org下载Web Beans的参考实现,然后解压。例如:

$ cd /Applications
$ unzip ~/jboss-5.0.0.GA.zip

然后从seamframework.org下载Web Beans的参考实现,然后解压。例如:

$ cd ~/
$ unzip ~/webbeans-1.0.0.ALPHA1.zip

然后,我们需要告诉Web Beans JBoss的位置。编辑jboss-as/build.properties,设置jboss.home属性。例如:

jboss.home=/Applications/jboss-5.0.0.GA

提示

例子的构建脚本包含多个目标:

  • ant restart - 以exploded形式部署例子

  • ant explode - 无需重新部署,更新一个exploded形式部署的例子

  • ant deploy - 以压缩jar包形式部署例子

  • ant undeploy - 将例子从服务器中移除

  • ant clean - 清除例子

部署猜数字(numberguess)例子:

$ cd examples/traslator
ant deploy

启动Tomcat:

jboss.home=/Applications/jboss-5.0.0.GA

提示

如果你使用Windows操作系统,则使用run.bat 脚本。

等待应用部署完毕,好好体验一下http://localhost:8080/webbeans-numberguess

在猜数字应用中,你有十次机会来猜一个1到100之间的数字。每次猜测之后,应用都会告诉你你猜的数字是高了还是低了。

猜数字应用由Web Beans,配置文件,Facelete JSF页面组成,打包为一个WAR。我们先看一下配置文件。

猜数字应用的所有的配置文件位于WEB-INF/,这个目录位于源码树的WebContent中。首先,我们在faces-config.xml文件中告诉JSF使用Faceletes:


<?xml version='1.0' encoding='UTF-8'?>
<faces-config version="1.2"
              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-facesconfig_1_2.xsd">
    
    <application>
        <view-handler
>com.sun.facelets.FaceletViewHandler</view-handler>
    </application>

</faces-config
>

这有一个空的web-beans.xml文件,标识这个应用是一个Web Beans应用。

最后,这有一个 web.xml

Let's take a look at the Facelet view:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
    xmlns:ui="http://java.sun.com/jsf/facelets"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:f="http://java.sun.com/jsf/core"
    xmlns:s="http://jboss.com/products/seam/taglib">

  <ui:composit(1)ion template="template.xhtml">
    <ui:define name="content">
       <h1
>Guess a number...</h1>
       <h:form(2) id="NumberGuessMain">
          <div style="color: red">
             <h:messages id="messages" globalOnly="false"/>
             <h:outputText id="Higher" value="Higher!" rendered="#{game.number gt game.guess and game.guess ne 0}"/>
             <h:outputText id="Lower" value="Lower!" rendered="#{game.number lt game.guess and game.guess ne 0}"/>
          </div>
   
          <div(3)>
             I'm thinking of a number between #{game.smallest} and #{game.biggest}.
             You have #{game.remainingGuesses} guesses.
          </div>
     
          <div>
             Y(4)our guess: 
             <h:inputText id="inputGuess" 
                          value="#{game.guess}" 
                          required="true" 
                          size="3" 
              (5)            disabled="#{game.number eq game.guess}">
                <f:validateLongRange maximum="#{game.biggest}" 
                                     minimum="#{game.smallest}"/>
             <(6)/h:inputText>
            <h:commandButton id="GuessButton"  
                             value="Guess" 
                             action="#{game.check}" 
                             disabled="#{game.number eq game.guess}"/>
          </div>
          <div>
            <h:commandButton id="RestartButton" value="Reset" action="#{game.reset}" immediate="true" />
          </div>
       </h:form>
    </ui:define>
  </ui:composition>
</html
>
1

Facelets is a templating language for JSF, here we are wrapping our page in a template which defines the header.

2

There are a number of messages which can be sent to the user, "Higher!", "Lower!" and "Correct!"

3

As the user guesses, the range of numbers they can guess gets smaller - this sentance changes to make sure they know what range to guess in.

4

This input field is bound to a Web Bean, using the value expression.

5

A range validator is used to make sure the user doesn't accidentally input a number outside of the range in which they can guess - if the validator wasn't here, the user might use up a guess on an out of range number.

6

And, of course, there must be a way for the user to send their guess to the server. Here we bind to an action method on the Web Bean.

这个例子包括4个类,前面两个是绑定类型。首先,这有一个@Random绑定类型,用来注入一个随机数:

@Target( { TYPE, METHOD, PARAMETER, FIELD })

@Retention(RUNTIME)
@Documented
@BindingType
public @interface Random {}

这还有一个@MaxNumber绑定类型,用来注入一个最大值:

@Target( { TYPE, METHOD, PARAMETER, FIELD })

@Retention(RUNTIME)
@Documented
@BindingType
public @interface MaxNumber {}

Generator类通过一个生产者(producer)方法创建一个随机数。它也通过一个生产者方法暴露可能的最大值:

@ApplicationScoped

public class Generator {
   
   private java.util.Random random = new java.util.Random( System.currentTimeMillis() );
   
   private int maxNumber = 100;
   
   java.util.Random getRandom()
   {
      return random;
   }
   
   @Produces @Random int next() { 
      return getRandom().nextInt(maxNumber); 
   }
   
   @Produces @MaxNumber int getMaxNumber()
   {
      return maxNumber;
   }
}

你会注意到Generator是应用范围的;因此我们不会每次都得到一个不同的随机对象。

最终的应用中的Web Bean是会话范围的 Game

你也许注意到我们使用了 @Named注释,以便我们能够通过EL(表达式语言)在JSF页面中使用Bean。最后,我们通过构造器注入来初始化猜数字游戏并给它设一个随机数。当然,在玩家猜对数字后,我们需要告诉玩家他赢了,所以我们通过FacesMessage反馈给玩家一条信息。

package org.jboss.webbeans.examples.numberguess;



import javax.annotation.PostConstruct;
import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;
import javax.webbeans.AnnotationLiteral;
import javax.webbeans.Current;
import javax.webbeans.Initializer;
import javax.webbeans.Named;
import javax.webbeans.SessionScoped;
import javax.webbeans.manager.Manager;
@Named
@SessionScoped
public class Game
{
   private int number;
   
   private int guess;
   private int smallest;
   private int biggest;
   private int remainingGuesses;
   
   @Current Manager manager;
   
   public Game()
   {
   }
   
   @Initializer
   Game(@MaxNumber int maxNumber)
   {      
      this.biggest = maxNumber;
   }
   public int getNumber()
   {
      return number;
   }
   
   public int getGuess()
   {
      return guess;
   }
   
   public void setGuess(int guess)
   {
      this.guess = guess;
   }
   
   public int getSmallest()
   {
      return smallest;
   }
   
   public int getBiggest()
   {
      return biggest;
   }
   
   public int getRemainingGuesses()
   {
      return remainingGuesses;
   }
   
   public String check()
   {
      if (guess
>number)
      {
         biggest = guess - 1;
      }
      if (guess<number)
      {
         smallest = guess + 1;
      }
      if (guess == number)
      {
         FacesContext.getCurrentInstance().addMessage(null, new FacesMessage("Correct!"));
      }
      remainingGuesses--;
      return null;
   }
   
   @PostConstruct
   public void reset()
   {
      this.smallest = 0;
      this.guess = 0;
      this.remainingGuesses = 10;
      this.number = manager.getInstanceByType(Integer.class, new AnnotationLiteral<Random
>(){});
   }
   
}

翻译器例子能够将你输入的句子翻译为拉丁文。

翻译器例子是一个EAR应用,包含EJBs和企业Beans。因此,它的结构比猜数字例子复杂。

首先,让我们看一下EAR聚合器,它位于webbeans-translator-ear模块下。Maven将为我们自动生成application.xmljboss-app.xml文件:


<plugin>
   <groupId
>org.apache.maven.plugins</groupId>
   <artifactId
>maven-ear-plugin</artifactId>
   <configuration>
      <modules>
         <webModule>
            <groupId
>org.jboss.webbeans.examples.translator</groupId>
            <artifactId
>webbeans-translator-war</artifactId>
            <contextRoot
>/webbeans-translator</contextRoot>
         </webModule>
      </modules>
      <jboss>
         <loader-repository
>webbeans.jboss.org:loader=webbeans-translator</loader-repository>
      </jboss>
   </configuration>
</plugin
>

我们需要在这里做些事情-首先我们需要设置上下文路径为一个不错的URL(http://localhost:8080/webbeans-translator),我们还需要将JBoss AS的类加载器隔离配置激活。

提示

如果你不使用Maven来生成这些文件,你将需要META-INF/jboss-app.xml


<?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
>webbeans-translator-ear</display-name>
  <description
>Ear Example for the reference implementation of JSR 299: Web Beans</description>
  
  <module>
    <web>
      <web-uri
>webbeans-translator.war</web-uri>
      <context-root
>/webbeans-translator</context-root>
    </web>
  </module>
  <module>
    <ejb
>webbeans-translator.jar</ejb>
  </module>
</application
>

然后,我们看一下WAR包。在猜数字例子中,我们需要faces-config.xml(配置Facelets)和一个位于WebContent/WEB-INF下的web.xml(配置JSF并将Web Beans服务加入Servlet容器中)。

更有意思的是用来翻译文本的facelet。在猜数字应用中我们有一个模板,这个模板套着表单(省略了表单):


<h:form id="NumberGuessMain">
            
   <table>
      <tr align="center" style="font-weight: bold" >
         <td>
            Your text
         </td>
         <td>
            Translation
         </td>
      </tr>
      <tr>
         <td>
            <h:inputTextarea id="text" value="#{translator.text}" required="true" rows="5" cols="80" />
         </td>
         <td>
            <h:outputText value="#{translator.translatedText}" />
         </td>
      </tr>
   </table>
   <div>
      <h:commandButton id="button" value="Translate" action="#{translator.translate}"/>
   </div>
   
</h:form
>

用户可以在左手边的文本区输入一些文本,点击翻译按钮查看右边的翻译结果。

最后,我们看一下EJB模块,webbeans-translator-ejb。在src/main/resources/META-INF下有两个配置文件,一个是空的web-beans.xml,用来标识这个档案包包含Web Beans,一个是ejb-jar.xml。Web Beans为所有的EJB提供注入和初始化服务,使用ejb-jar.xml文件来配置。你将在使用Web Beans的EJB项目中需要这些配置:

我们将最有意思的部分放在最后,那就是代码!这个例子有两个简单Beans, SentanceParserTextTranslator,还有两个企业Beans,TanslatorControllerBeanSentenceTranslator。现在你应该对Web Beans有点熟悉了,我们在这里着重最有意思的部分。

SentanceParserTextTranslator是相互依赖的Beans,TextTranslator使用构造器初始化:

public class TextTranslator { 

   private SentenceParser sentenceParser; 
   private Translator sentenceTranslator; 
   
   @Initializer
   TextTranslator(SentenceParser sentenceParser, Translator sentenceTranslator) 
   { 
      this.sentenceParser = sentenceParser; 
      this.sentenceTranslator = sentenceTranslator;

TextTranslator是一个无状态Bean(拥有一个本地业务接口),这里是魔术展现的地方-当然,我们不会开发一个完整的翻译器,但我们可以开发一个不错的小玩意!

最后,这里又要一个面向UI的控制器,从用户输入处搜集文本,转发给翻译器。这个控制器是请求范围的,具名的,有状态的会话Bean,它可以将翻译器注入进来。

@Stateful

@RequestScoped
@Named("translator")
public class TranslatorControllerBean implements TranslatorController
{
   
   @Current TextTranslator translator;

这个Bean也拥有页面上所有域的getter和setter方法。

这个Bean是有状态会话Bean,我们需要有一个remove方法:

   @Remove

   public void remove()
   {
      
   }

Web Beans管理器在这个bean销毁的时候调用remove方法;在这个例子中是请求结束的时候。

Web Beans参考实现的例子到此结束。想要获得关于参考实现更多的知识或者帮助,请访问http://www.seamframework.org/WebBeans/Development

我们在各个方面都需要帮助-bug修复,新特性开发,例子开发和参考指南的翻译等等。

Web Beans支持三种主要的依赖注入机制:

构造器参数注入:

public class Checkout {

        
    private final ShoppingCart cart;
    
    @Initializer
    public Checkout(ShoppingCart cart) {
        this.cart = cart;
    }
}

初始化 方法参数注入:

public class Checkout {

        
    private ShoppingCart cart;
    @Initializer 
    void setShoppingCart(ShoppingCart cart) {
        this.cart = cart;
    }
    
}

和直接的域注入:

public class Checkout {


    private @Current ShoppingCart cart;
    
}

当Web Bean实例被首次初始化时,依赖注入总是随之发生。

  • 首先,Web Bean管理器调用Web Bean构造器来获得一个Web Bean的实例。

  • 接下来,Web Bean管理器初始化这个Web bean的所有注入域的值。

  • 然后,Web Bean管理器调用这个Web Bean的初始化方法。

  • 最后, 如果有 @PostConstruct 方法的话,调用这个方法。

EJB Beans不支持构造器参数注入,因为EJB是由EJB容器负责实例化,而不是Web Bean管理器负责。

当应用默认绑定类型@Current时,构造器的参数和初始化方法需要显式地注释。然而,即使应用了默认的绑定类型,注入域 也必须 指定一个绑定类型。如果一个域没有指定绑定类型,这个域将不会有任何注入发生。

生产者方法也支持参数注入:

@Produces Checkout createCheckout(ShoppingCart cart) {

    return new Checkout(cart);
}

最后,观察者方法(我们将在 第 9 章 事件一章中讨论),清除(disposal)方法和解构(destructor)方法都只支持参数注入。

Web Beans规范定义了一个称为 类型安全解析算法的过程,当在注入点识别所注入的Web Bean时,Web Bean管理器会遵循这个过程。这个算法初看起来非常复杂,然而你一旦理解了它,它就相当直观。类型安全解析在系统初始化的时候执行,这意味着如果Web Bean的依赖无法被满足的时候,管理器将立刻通知用户,抛出一个 UnsatisfiedDependencyException 异常或者 AmbiguousDependencyException 异常。

这个算法的目的是允许多个Web Beans实现相同的API类型,并且:

  • 允许客户通过 绑定注释 选择它需要的具体实现,

  • 允许应用部署者激活或者关闭 部署类型 ,从而实现在特定的部署环境下选择适当的具体实现,而无需修改客户,或者

  • 使用部署类型优先级 来允许一个API的实现在部署时重载相同API的另一个实现,而无需修改客户。

让我们看看Web Bean管理器如何决定注入哪个Web Bean。

如果我们拥有实现特定API类型的多个Web Bean,我们可以使用一个绑定注释来指定在注入点注入哪个Web Bean。例如,我们可能有两个 PaymentProcessor 的实现:

@PayByCheque

public class ChequePaymentProcessor implements PaymentProcessor {
    public void process(Payment payment) { ... }
}
@PayByCreditCard

public class CreditCardPaymentProcessor implements PaymentProcessor {
    public void process(Payment payment) { ... }
}

其中 @PayByCheque@PayByCreditCard 是绑定注释:

@Retention(RUNTIME)

@Target({TYPE, METHOD, FIELD, PARAMETER})
@BindingType
public @interface PayByCheque {}
@Retention(RUNTIME)

@Target({TYPE, METHOD, FIELD, PARAMETER})
@BindingType
public @interface PayByCreditCard {}

一个客户Web Bean开发者使用绑定注释来指定到底哪个Web Bean应该被注入。

使用域注入:

@PayByCheque PaymentProcessor chequePaymentProcessor;

@PayByCreditCard PaymentProcessor creditCardPaymentProcessor;

使用初始化方法注入:

@Initializer

public void setPaymentProcessors(@PayByCheque PaymentProcessor chequePaymentProcessor, 
                                 @PayByCreditCard PaymentProcessor creditCardPaymentProcessor) {
   this.chequePaymentProcessor = chequePaymentProcessor;
   this.creditCardPaymentProcessor = creditCardPaymentProcessor;
}

或者使用构造器注入:

@Initializer

public Checkout(@PayByCheque PaymentProcessor chequePaymentProcessor, 
                @PayByCreditCard PaymentProcessor creditCardPaymentProcessor) {
   this.chequePaymentProcessor = chequePaymentProcessor;
   this.creditCardPaymentProcessor = creditCardPaymentProcessor;
}

所有的Web Beans都有一个 部署类型 。每个部署类型标识一套Web Beans,这套Web Beans会有条件地在某些系统部署下面被安装。

例如,我们可以定义一套为名为 @Mock 的部署类型,这种部署类型用来标识只应该在整合测试环境下才在系统中安装的Web Beans:

@Retention(RUNTIME)

  @Target({TYPE, METHOD})
  @DeploymentType
  public @interface Mock {}

假定我们有一些和外部系统交互的Web Beans来处理付费:

public class ExternalPaymentProcessor {

        
    public void process(Payment p) {
        ...
    }
    
}

因为这个Web Bean并不显式地指定一个部署类型,因此它有一个默认的 @Production 部署类型。

在整合测试或者单元测试期间,外部系统可能会很慢或者无法获得,所以我们可能会创建一个模拟对象:

@Mock 

public class MockPaymentProcessor implements PaymentProcessor {
    @Override
    public void process(Payment p) {
        p.setSuccessful(true);
    }
}

但是Web Bean管理器如何决定在一个特定部署中使用哪一个实现?

如果你注意的话,你可能想知道Web Bean如何决定使用哪个实现— ExternalPaymentProcessor 还是 MockPaymentProcessor —。思考一下当管理器遇到这个注入点时会发生什么:

@Current PaymentProcessor paymentProcessor

现在有两个Web Bean满足 PaymentProcessor 合约。当然,我们无法使用一个绑定注释来消除这个歧义,因为绑定注释在注入点被硬编码到了源代码中,并且我们希望管理器在部署时能够决定注入哪一个Web Bean!

这个问题的解决方案是每个不同的部署类型都拥有不同的 优先级 。部署类型的优先级由它们在 web-beans.xml 中出现的顺序决定。在我们这个例子中, @Mock@Production 出现的晚,所以它拥有更高的优先级。

无论何时管理器发现有多个Web Bean能够满足一个注入点指定的合约(API类型加上绑定注释),它都会考虑Web Beans的相对优先级。它将注入拥有更高优先级的Web Bean。在我们这个例子中,当系统运行在整合测试环境中(这是我们想要的),Web Bean管理器将注入 MockPaymentProcessor 对象。

将其和当今流行的管理器体系比较是很有意思的。各种 "轻量级"的容器也许也可以支持条件化部署在类路径中的类,但是这些需要部署的类必须显式地,逐个地列在配置代码或某个XML配置文件中。Web Bean当然支持通过XML来定义和配置Web Bean,但是多数情况下,只要不需要复杂的配置,部署类型可以使用一行XML就能配置一整套Web Beans。同时,浏览代码的开发者可以很容易识别这些Web Bean应该部署在哪些场景中。

注入的Web Bean的客户通常不会直接拥有这个Web bean实例的引用。

想象一下如果一个应用范围的Web Bean能够拥有一个请求范围的Web Bean的直接引用。应用范围的Web Bean是被很多不同的请求共享的。但是不同的请求应该看到不同的请求范围的Web Bean实例!

现在再想象一个会话范围的Web Bean拥有一个应用范围的Web Bean的直接引用,会话上下文常常被序列化到硬盘以便更高效的使用内存。但是,应用范围的Web Bean实例不能和会话范围的Web Bean一起被序列化!

因此,除非Web Bean使用默认的 @Dependent 的范围,Web Bean管理器必须通过一个代理对象来间接地拥有所有注入的Web Bean引用。这个 客户代理 负责确保收到方法调用的Web Bean实例就是当前上下文相关联的实例。客户代理也允许诸如绑定到会话上下文的Web Bean可以序列化到硬盘中,而无需递归地序列化注入到这个Web Bean中的其他的Web Bean。

不幸的是,由于Java语言的限制,一些Java类型无法被Web Bean管理器代理。因此,如果注入点的类型无法被代理的话,Web Bean管理器会抛出一个 UnproxyableDependencyException 异常。

下面的Java类型无法被Web Bean管理器代理:

修正 UnproxyableDependencyException 很容易。只需简单的想注入类添加一个无参构造器,引入一个接口或者将注入的Web Bean的范围 @Dependent 即可。

我们有一些依赖对象— @Dependent 范围的Web Bean — 需要知道它们所注入的对象或者注入点的信息,以便能够实现其功能。例如:

一个 @Dependent 范围的Web Bean可以注入一个 InjectionPoint 实例并且访问这个注入点相关的元数据。

我们看一个例子。下面的代码很冗长脆弱,有重构问题:

Logger log = Logger.getLogger(MyClass.class.getName());

这个生产者方法允许你注入一个JDK的 Logger ,没有显式的指定一个日志分类:

class LogFactory {


   @Produces Logger createLogger(InjectionPoint injectionPoint) { 
      return Logger.getLogger(injectionPoint.getMember().getDeclaringClass().getName()); 
   }
}

我们现在可以编写:

@Current Logger log;

没被说服?我们还有第二个例子。要注入HTTP参数,我们需要定一个绑定类型:

@BindingType

@Retention(RUNTIME)
@Target({TYPE, METHOD, FIELD, PARAMETER})
public @interface HttpParam {
   @NonBinding public String value();
}

我们可以在注入点使用这个绑定类型:

@HttpParam("username") String username;

@HttpParam("password") String password;

下面的生产方法能够完成这个工作:

class HttpParams


   @Produces @HttpParam("")
   String getParamValue(ServletRequest request, InjectionPoint ip) {
      return request.getParameter(ip.getAnnotation(HttpParam.class).value());
   }
}

(注意 HttpParam 注释的成员 value() 将被Web Bean管理器忽略,因为它拥有 @NonBinding. 注释)

Web Bean管理器提供一个内置的实现 InjectionPoint 接口的Web Bean:

public interface InjectionPoint { 

   public Object getInstance(); 
   public Bean<?> getBean(); 
   public Member getMember(): 
   public <extends Annotation
> T getAnnotation(Class<T
> annotation); 
   public Set<extends Annotation
> getAnnotations(); 
}

目前为止,我们已经看到了几个 范围类型注释的例子。Web Bean的范围决定了Web Bean实例的生命周期。范围还决定了哪个客户端引用了哪个Web Bean实例。根据Web Beans规范,一个范围决定:

  • 该范围的Web Bean的一个实例何时被创建

  • 该范围的Web Bean实例何时被销毁

  • 注入的引用指向该范围的Web Bean的哪个实例

例如,如果我们有一个会话范围的Web Bean:CurrentUser。那么在同一个HttpSession的上下文中调用的所有的Web Bean都将看到同一个CurrentUser实例。这个实例在会话第一次需要CurrentUser时被自动创建,在会话结束时被自动销毁。

Web Beans的对话(Conversation)范围有点类似与传统的会话范围(Session),传统的会话范围常常用来存储和系统用户相关的状态,并且能够跨越多个请求。然而,对话范围还有很多地方和会话范围不一样:

从用户角度出发,一个对话代表一个任务,或者一个工作单元。用户当前工作相关的状态由对话上下文维护。如果用户同时处理多个事情,就会有多个对话与之对应。

一个对话上下文在任何JSF请求中都是激活的。但是,大部分对话都在请求结束的时候被销毁了。如果一个对话需要跨越多个请求来维护状态的话,它必须显式地升级为长时对话

除了内置的四个范围,Web Beans还提供了一个依赖的伪范围。这个范围是没有显式设置范围类型的Web Bean的默认范围。

例如,这个Web Bean有一个范围类型@Dependent:

public class Calculator { ... }

当一个注入点被解析为一个依赖的Web Bean之后,每当第一个Web Bean被初始化时,都会创建一个依赖的Web Bean实例。不同的Web Beans或者不同的注入点的依赖的Web Beans的实例都不会被共享。它们是其它Web Bean实例的依赖的对象

依赖的Web Bean实例在它们所依赖对象实例销毁的时候被销毁。

Web Bean能够让我们轻松获得一个Java类或者EJB Bean的依赖实例,甚至这个类或者EJB Bean已经被声明为一个其他范围的Web Bean也没问题。

生产者方法能够让我们克服使用Web Bean管理器代替应用来负责实例化对象所带来的特定的限制。生产者方法也是将非Web Beans的对象整合到Web Beans环境中的最简单的途径。(我们将在第 12 章 使用XML定义Web Bean讨论第二种方法)

根据规范:

A Web Beans producer method acts as a source of objects to be injected, where:

  • the objects to be injected are not required to be instances of Web Beans,

  • the concrete type of the objects to be injected may vary at runtime or

  • the objects require some custom initialization that is not performed by the Web Bean constructor

For example, producer methods let us:

  • expose a JPA entity as a Web Bean,

  • expose any JDK class as a Web Bean,

  • define multiple Web Beans, with different scopes or initialization, for the same implementation class, or

  • vary the implementation of an API type at runtime.

In particular, producer methods let us use runtime polymorphism with Web Beans. As we've seen, deployment types are a powerful solution to the problem of deployment-time polymorphism. But once the system is deployed, the Web Bean implementation is fixed. A producer method has no such limitation:

@SessionScoped

public class Preferences {
    
    private PaymentStrategyType paymentStrategy;
    
    ...
    
    @Produces @Preferred 
    public PaymentStrategy getPaymentStrategy() {
        switch (paymentStrategy) {
            case CREDIT_CARD: return new CreditCardPaymentStrategy();
            case CHEQUE: return new ChequePaymentStrategy();
            case PAYPAL: return new PayPalPaymentStrategy();
            default: return null;
        } 
    }
    
}

Consider an injection point:

@Preferred PaymentStrategy paymentStrat;

This injection point has the same type and binding annotations as the producer method, so it resolves to the producer method using the usual Web Beans injection rules. The producer method will be called by the Web Bean manager to obtain an instance to service this injection point.

.

上面的代码有一个潜在的问题。 CreditCardPaymentStrategy 的实现使用Java的 new 操作符来实例化。应用直接实例化的对象无法获得依赖注入的好处,也无法获得拦截器。

如果这不是我们想要的,我们可以对生产者方法使用依赖注入来获得Web Bean实例:

@Produces @Preferred @SessionScoped

public PaymentStrategy getPaymentStrategy(CreditCardPaymentStrategy ccps,
                                          ChequePaymentStrategy cps,
                                          PayPalPaymentStrategy ppps) {
    switch (paymentStrategy) {
        case CREDIT_CARD: return ccps;
        case CHEQUE: return cps;
        case PAYPAL: return ppps;
        default: return null;
    } 
}

等一下,如果 CreditCardPaymentStrategy 是一个请求范围的Web Bean,那会怎样?生产者方法将会把当前请求范围的实例"提升"到会话范围。这近乎是一个Bug!请求范围的对象会在会话结束前被Web Bean管理器销毁,但是这个对象的引用将被 "悬挂"在会话范围。Web Bean管理器不会检测到这种错误,因此使用生产者方法返回Web bean实例的时候一定要多加小心!

我们至少有三种方式来解决这个Bug。我们可以改变 CreditCardPaymentStrategy实现的范围,但是这种方法将影响到其他使用这个Web Bean的客户。一个更好的选择是将生产者方法的范围更改到 @Dependent 或者 @RequestScoped

不过更加常用的方法是使用特殊的 @New 绑定注释。

Web Bean的首要宗旨就是松耦合。我们已经看到三种实现松耦合的方法:

这些技术都是为了将客户端和服务器端解耦。客户端不在和一个API的具体实现紧密绑定,也不需要管理服务器端对象的生命周期。这种方法能够让有状态的对象像服务一样交互

松耦合能够让系统更加动态。系统可以以定义良好的方式来相应变化。在过去,多数框架总是通过牺牲类型安全来提供上述功能。实现此种高度的松耦合,同时又能够保证类型安全,Web Beans是第一个实现这个目标的技术。

Web Bean提供三种额外的功能来实现松耦合的目标:

让我们首先研究拦截器。

Web Beans重用了EJB3.0的基本的拦截器体系,并且在两个方向上扩展了其功能:

  • 任何Web Bean都可以拥有拦截器,而不仅仅是会话Bean。

  • Web Beans拥有一个更复杂的基于注释的方法将拦截器绑定到Web Bean上。

EJB规范定义了两种拦截点:

  • 业务方法拦截,和

  • 生命周期回调方法拦截。

一个 业务方法拦截 是Web Bean的客户应用在Web Bean的方法调用上:

public class TransactionInterceptor {

    @AroundInvoke public Object manageTransaction(InvocationContext ctx) { ... }
}

一个 生命周期回调拦截器 是容器应用在生命周期回调方法的调用上:

public class DependencyInjectionInterceptor {

    @PostConstruct public void injectDependencies(InvocationContext ctx) { ... }
}

一个拦截器类既可以拦截生命周期回调方法,也可以拦截业务方法。

拦截器能够以一种强大的方式来捕获和分离关注点,拦截器和类型系统的关系是正交的。任何拦截器都能够拦截任何Java类型的调用。这种特性能够让拦截器很好的处理技术关注点,例如事务管理和安全。不过,拦截器无法领会其所拦截时间的真实语义。因此,拦截器并不是分离业务相关的关注点的好工具。

对装饰器来说,反之亦然。一个装饰器只拦截特定Java接口的调用,因此它能够领会这个接口关联的语义。装饰器的特性使其成为某些业务关注点的理想的建模工具。这也意味着装饰器并不拥有一个拦截器的普遍性。装饰器无法解决跨越多个不同类型的技术关注点。

假定我们有一个表现帐户的接口:

public interface Account {

    public BigDecimal getBalance();
    public User getOwner();
    public void withdraw(BigDecimal amount);
    public void deposit(BigDecimal amount);
}

我们系统实现中可以有多个不同的Web Beans实现 Account 接口。然而我们有一个通用的法律要求对于任何帐户,大的交易必须由系统在一个特定的日志中记录。装饰器非常适合处理这种工作。

装饰器是一个简单的Web Bean,能够实现其装饰的类型。装饰器使用 @Decorator 注释。

@Decorator

public abstract class LargeTransactionDecorator 
        implements Account {
    
    @Decorates Account account;
    
    @PersistenceContext EntityManager em;
    
    public void withdraw(BigDecimal amount) {
        account.withdraw(amount);
        if ( amount.compareTo(LARGE_AMOUNT)
>0 ) {
            em.persist( new LoggedWithdrawl(amount) );
        }
    }
    
    public void deposit(BigDecimal amount);
        account.deposit(amount);
        if ( amount.compareTo(LARGE_AMOUNT)
>0 ) {
            em.persist( new LoggedDeposit(amount) );
        }
    }
    
}

和其他简单的Web Beans不同,一个装饰器可以是一个抽象类。如果被装饰的接口中一个特殊方法对装饰器没有用处,那么装饰器可以不实现这个方法。

Web Beans事件通知机制能够让Web Beans以完全解耦的方式交互。事件产生者(producers)触发事件,然后由Web Bean管理器发送给事件 观察者(observers) 。基本的模式和我们常见的观察者设计模式类似,但也有一些曲解:

  • 不仅是事件产生者和观察者解耦;观察者和产生者也完全解耦,

  • 观察者可以指定一个"选择器"组合来限定接受的事件通知,并且

  • 观察者可以即刻被通知,也可以指定只有当前事务结束后再发送事件通知。

事件生产者可以通过注入获得一个 事件通知者对象:

@Observable Event<Document

> documentEvent

@Observable注释隐式指定了一个 @Dependent 范围的,部署类型为@Standard 的Web Bean。其实现由Web Bean管理器提供。

一个生产者通过调用 Event 接口的 fire() 方法来触发事件,传递一个 事件对象

documentEvent.fire(document);

一个事件对象可以是没有任何类型变量或者通配类型参数的任何Java类的实例。事件可以通过符合下面条件的观察者方法传送:

Web Bean管理器简单地调用所有观察者方法,以事件参数值来传递事件对象。如果任何观察者方法抛出一个异常,Web Bean管理器将停止调用观察者方法,fire() 方法再次抛出这个异常。

要指定"选择器",事件生产者可以将一个事件绑定类型的实例传递给 fire() 方法:

documentEvent.fire( document, new AnnotationLiteral<Updated

>(){} );

AnnotationLiteral 帮助类能够让我们内部实例化绑定类型,这在Java里很难这么做。

事件将被发送给每个符合下面条件的观察者方法:

或者,通过注释事件通知者注入点来指定事件绑定:

@Observable @Updated Event<Document

> documentUpdatedEvent

然后,每个通过 Event 实例触发的事件都有注释的事件绑定。事件将被发送给每个符合下面条件的观察者方法:

事务性的观察者在事件触发的事务完成阶段之前或者之后接收到事件通知。例如下面的观察者方法需要刷新在应用上下文中缓存的查询结果集,但只有在事务成果地更新了 Category 树之后才能发生:

public void refreshCategoryTree(@AfterTransactionSuccess @Observes CategoryUpdateEvent event) { ... }

我们有三种事务性观察者:

事务性的观察者在诸如Web Beans这类具有状态的对象模型中非常重要,因为状态通常比一个单一的原子事务所保留的时间更长。

设想一下我们已经在应用范围内缓存了一个JPA的查询结果集:

@ApplicationScoped @Singleton

public class Catalog {
    @PersistenceContext EntityManager em;
    
    List<Product
> products;
    @Produces @Catalog 
    List<Product
> getCatalog() {
        if (products==null) {
            products = em.createQuery("select p from Product p where p.deleted = false")
                .getResultList();
        }
        return products;
    }
    
}

一个 Product时常会被创建或者删除。当这个动作发生后,我们需要刷新 Product 分类。但是我们需要在事务完全成功之后才能处理刷新。

创建或者删除 Product 的Web Bean应该触发事件,例如:

@Stateless

public class ProductManager {
    @PersistenceContext EntityManager em;
    @Observable Event<Product
> productEvent;
    public void delete(Product product) {
        em.delete(product);
        productEvent.fire(product, new AnnotationLiteral<Deleted
>(){});
    }
    
    public void persist(Product product) {
        em.persist(product);
        productEvent.fire(product, new AnnotationLiteral<Created
>(){});
    }
    
    ...
    
}

现在 Catalog 能够观察到事务成功完成之后触发的事件:

@ApplicationScoped @Singleton

public class Catalog {
    ...
    
    void addProduct(@AfterTransactionSuccess @Observes @Created Product product) {
        products.add(product);
    }
    
    void addProduct(@AfterTransactionSuccess @Observes @Deleted Product product) {
        products.remove(product);
    }
    
}

Web Bean的第二个宗旨是强类型。Web Bean有关依赖,拦截器和装饰器的信息以及事件生产者对应的事件消费者的信息都包含在类型安全的Java构件中,可以被编译器验证。

在Web Bean代码中,你不会看到基于字符串的标识符。这不是因为这个框架使用智能默认规则 — 也叫"按惯例配置" — 将它们隐藏起来,这是因为从开始就没有任何字符串!

这种方法的显而易见的好处是任何IDE都可以提供自动完成,验证和重构而无需任何特殊工具。除此之外,还有第二个,不是那么明显的好处。它能够让你考虑识别对象,事件或者拦截器的时候使用注释,而不是名字,这样你将可以提升你的代码的语义水平。

Web Beans能够鼓励你为概念建模,开发注释。例如:

而不是使用复合名字,像:

注释可以重用。它们能够描述系统不同部分的通用特征。它们能够帮助我们分类和理解我们的代码。它们能够以通用的方式为我们处理共同的关注点。它们能够让我们的代码具有更高的可读性,更加容易被理解。

Web Beans的模板是这种思想的延伸。一个模板为我们应用体系中的一个通用的角色建模。它将一个角色的各种不同的属性,包括范围,拦截器绑定,部署类型,等等封装到一个单一的可重用的包中。

甚至Web Bean的XML元数据也是强类型的!对于XML没有编译器,Web Bean充分利用了XML模式去验证XML中出现的Java类型和属性。这种方式让XML更加具有可读性,就像注释让我们的Java代码更加具有可读性一样。

我们现在可以接触Web Bean中更加高级的特性了。要记住这些特性能够让我们的代码不仅更容易验证,也更容易理解。大部分时间,你根本不需要这些特性,但是如果你能聪明地运用它们的话,你将充分体会到它们的强大。

根据Web Bean规范:

在很多系统中,体系模式的使用会产生一套重复发生的Web Bean角色。一个模板允许一个框架开发者来识别这样的一个角色,并且在一个中心地方为拥有这个角色的Web Bean声明一些通用的元数据。

一个模板封装了下面的任何组合:

  • 一个默认的部署类型,

  • 一个默认的范围类型,

  • 一个对于Web Bean范围的限制

  • 一个Web Bean实现或者继承一个特定类型的需求,以及

  • 一套拦截器绑定注释。

一个模板也可以指定所有使用这个模板的Web Bean拥有的默认的Web Bean名称。

一个Web Bean可以声明零个,一个或者多个模板。

一个模板其实就是一个Java注释类型。这个模板在一些MVC框架中识别动作类:

@Retention(RUNTIME)

@Target(TYPE)
@Stereotype
public @interface Action {}

我们注释来让Web Bean应用模板

@Action 

public class LoginAction { ... }

我们已经看到Web Bean的依赖注入模型如何让我们在部署时期 重载 一个API实现。例如,下面的企业级Web Bean在生产环境下使提供一个PaymentProcessor接口的实现:

@CreditCard @Stateless

public class CreditCardPaymentProcessor 
        implements PaymentProcessor {
    ...
}

但在我们的阶段开发环境中,我们可以使用一个不同的Web Bean来重载这个PaymentProcessor 实现:

@CreditCard @Stateless @Staging

public class StagingCreditCardPaymentProcessor 
        implements PaymentProcessor {
    ...
}

我们试图做的是在特定的系统部署环境中使用StagingCreditCardPaymentProcessor来完全替代AsyncPaymentProcessor。在这个部署中,部署类型为@Staging的将有比默认部署类型@Production更高的优先级,因此下面注入点的客户:

@CreditCard PaymentProcessor ccpp

将会收到一个 StagingCreditCardPaymentProcessor 的实例。

不幸的是,我们很容易掉入几个陷阱:

  • 更高优先级的Web Bean可能没有实现其要重载的所有的API类型,

  • 更高优先级的Web Bean可能没有声明其要重载的所有绑定类型,

  • 更高优先级的Web Bean可能没有和其要重载的Web Bean拥有相同的名字,或者

  • 其要重载的Web Bean有可能声明了一个生产者方法,清除方法或者观察者方法。

在上面的任何一种情况,我们试图重载的Web Bean仍旧可以在运行时被调用。因此重载对于开发者来说是很容易出错的。

Web Bean提供了一个特殊的特性,称之为特化,能够帮助开发者避免这些陷阱。特化初看起来有些神秘,但是在实际应用中非常容易使用,你很快就会感激它提供的特殊的安全性。

目前为止,我们看了大量使用注释来声明Web Bean的例子。然而,我们有时候并不使用注释来定义Web Bean,如下列情况:

  • 当实现类是从先前已经存在的类库中产生的时候,或者

  • 多个Web Bean对应同一个实现类的时候。

在上述任何一种情况下,Web Bean给我们两种选择:

  • 写一个生产者方法,或者

  • 使用XML来声明Web Bean。

很多框架使用XML来提供Java类相关的元数据。然而,Web Bean使用了和大多其他框架不同的方法来指定Java类的名字,域或者方法。Web Bean让你使用类或者成员名作为XML元素名,而不是将类和成员名作为XML元素的字符串值来声明Web Bean。

这种方法的好处是你可以使用XML模式来验证XML, 阻止XML文档中的拼写错误。它甚至可以让一个工具从编译好的Java代码中自动生成XML模式。或者一个整合开发环境(IDE)可以直接进行验证,无需使用显式的中间生成步骤。

Web Bean的第三个宗旨是整合。Web Bean设计思想就是能够和其他的技术相互合作,能够让开发者更容易地将其他技术结合使用。Web Bean是一个开发的技术。它形成了Java EE生态系统的一部分,而它本身也是一个新的生态系统的基石,这个新的生态系统能够为已有的框架和技术提供一个更便携的扩展和整合机制。

我们已经看到了Web Bean如何帮助整合EJB和JSF, 允许我们将EJB直接绑定到JSF页面上。而这只是开始,Web Bean为其他技术提供了同样的潜在能力,例如业务流程管理引擎,其他的Web框架,第三方组件模型。Java EE平台永远不可能将Java应用开发世界中所有有意思的技术都标准化。但是Web Bean能够提供一个便捷的方式将这些尚未成为平台一部分的技术更加容易并且无缝地集成到Java EE环境中。

我们已经看到了如何在应用中使用Web Bean来充分利用Java EE平台。我们也粗略地看到了一些能够支持Web Bean的便携性扩展点的SPI。你也许永远不回直接使用这些SPI,但是,知道在需要的时候你可以使用它们很有好处。最重要的是,每次你使用一个第三方扩展的时候,都有可能间接地利用了它们。

Web Beans被完全地整合进了Java EE环境里。Web Beans 能够访问Java EE资源和JPA持久化上下文。它们可以在JSF和JSP页面中的统一表达式语言使用。它们甚至可以被注入到一些对象中,例如Servlet和消息驱动Bean,而这些对象都不是Web Bean。

使用JMS发送消息有点复杂,因为你需要处理很多不同的对象。对于队列(Queue),我们有Queue, QueueConnectionFactory, QueueConnection, QueueSessionQueueSender。对于主题(Topic)有Topic, TopicConnectionFactory, TopicConnection, TopicSessionTopicPublisher。这些对象每个都有自己的生命周期和线程模型,我们都需要考虑这些问题。

Web Beans为我们处理所有这些问题。我们只须在 web-beans.xml声明队列或者主题,指定一个关联的绑定类型和连接工厂即可。


<Queue>
    <destination
>java:comp/env/jms/OrderQueue</destination>
    <connectionFactory
>java:comp/env/jms/QueueConnectionFactory</connectionFactory>
    <myapp:OrderProcessor/>    
</Queue
>

<Topic>
    <destination
>java:comp/env/jms/StockPrices</destination>
    <connectionFactory
>java:comp/env/jms/TopicConnectionFactory</connectionFactory>
    <myapp:StockPrices/>    
</Topic
>

现在,对于队列,我们只须注入 Queue, QueueConnection, QueueSession 或者 QueueSender;对于主题,我们只须注入Topic, TopicConnection, TopicSession 或者 TopicPublisher

@OrderProcessor QueueSender orderSender;

@OrderProcessor QueueSession orderSession;
public void sendMessage() {
    MapMessage msg = orderSession.createMapMessage();
    ...
    orderSender.send(msg);
}
@StockPrices TopicPublisher pricePublisher;

@StockPrices TopicSession priceSession;
public void sendMessage(String price) {
    pricePublisher.send( priceSession.createTextMessage(price) );
}

被注入的JMS对象的生命周期完全由Web Bean管理器控制。

Web Beans意图成为框架的平台,扩展的平台,和能够与其他技术整合的平台。因此,Web Bean将一套SPI暴露给开发者,从而能够让他们便于扩展Web Bean。例如,下面是Web Bean设计者设想的扩展类型:

  • 整合业务流程管理引擎,

  • 整合第三方框架,例如Spring, Seam, GWT或者Wicket,和

  • 基于Web Bean编程模型的新技术。

扩展Web Bean的神经中枢是 Manager 对象。

Manager 接口能够让我们通过编程来注册和获得Web Bean,拦截器,装饰器,观察者和上下文。

public interface Manager

{
   public <T
> Set<Bean<T
>
> resolveByType(Class<T
> type, Annotation... bindings);
   public <T
> Set<Bean<T
>
> resolveByType(TypeLiteral<T
> apiType,
         Annotation... bindings);
   public <T
> T getInstanceByType(Class<T
> type, Annotation... bindings);
   public <T
> T getInstanceByType(TypeLiteral<T
> type,
         Annotation... bindings);
   public Set<Bean<?>
> resolveByName(String name);
   public Object getInstanceByName(String name);
   public <T
> T getInstance(Bean<T
> bean);
   public void fireEvent(Object event, Annotation... bindings);
   public Context getContext(Class<? extends Annotation
> scopeType);
   public Manager addContext(Context context);
   public Manager addBean(Bean<?> bean);
   public Manager addInterceptor(Interceptor interceptor);
   public Manager addDecorator(Decorator decorator);
   public <T
> Manager addObserver(Observer<T
> observer, Class<T
> eventType,
         Annotation... bindings);
   public <T
> Manager addObserver(Observer<T
> observer, TypeLiteral<T
> eventType,
         Annotation... bindings);
   public <T
> Manager removeObserver(Observer<T
> observer, Class<T
> eventType,
         Annotation... bindings);
   public <T
> Manager removeObserver(Observer<T
> observer,
         TypeLiteral<T
> eventType, Annotation... bindings);
   public <T
> Set<Observer<T
>
> resolveObservers(T event, Annotation... bindings);
   public List<Interceptor
> resolveInterceptors(InterceptionType type,
         Annotation... interceptorBindings);
   public List<Decorator
> resolveDecorators(Set<Class<?>
> types,
         Annotation... bindings);
}

我们可以通过注入来获得一个 Manager 实例:

@Current Manager manager

Web Beans is the reference implementation of JSR-299, and is used by JBoss AS and Glassfish to provide JSR-299 services for Java Enterprise Edition applications. Web Beans also goes beyond the environments and APIs defined by the JSR-299 specification and provides support for a number of other environments (such as a servlet container such as Tomcat, or Java SE) and additional APIs and modules (such as logging, XSD generation for the JSR-299 XML deployment descriptors).

If you want to get started quickly using Web Beans with JBoss AS or Tomcat and experiment with one of the examples, take a look at 第 3 章 使用Web Beans的参考实现. Otherwise read on for a exhaustive discussion of using Web Beans in all the environments and application servers it supports, as well the Web Beans extensions.

Web Beans can be used in Tomcat 6.0.

Web Beans should be used as a web application library in Tomcat. You should place webbeans-tomcat.jar in WEB-INF/lib. webbeans-tomcat.jar is an "uber-jar" provided for your convenience. Instead, you could use its component jars:

You also need to explicitly specify the Tomcat servlet listener (used to boot Web Beans, and control its interaction with requests) in web.xml:

<listener>
   <listener-class
>org.jboss.webbeans.environment.servlet.Listener</listener-class>
</listener
>

Tomcat has a read-only JNDI, so Web Beans can't automatically bind the Manager. To bind the Manager into JNDI, you should add the following to your META-INF/context.xml:

<Resource name="app/Manager" 
          auth="Container"
          type="javax.inject.manager.Manager"
          factory="org.jboss.webbeans.resources.ManagerObjectFactory"/>

and make it available to your deployment by adding this to web.xml:

<resource-env-ref>
  <resource-env-ref-name>
    app/Manager
  </resource-env-ref-name>
  <resource-env-ref-type>
    javax.inject.manager.Manager
  </resource-env-ref-type>
</resource-env-ref>

Tomcat only allows you to bind entries to java:comp/env, so the Manager will be available at java:comp/env/app/Manager

Web Beans also supports Servlet injection in Tomcat. To enable this, place the webbeans-tomcat-support.jar in $TOMCAT_HOME/lib, and add the following to your META-INF/context.xml:

<Listener className="org.jboss.webbeans.environment.tomcat.WebBeansLifecycleListener" />

Apart from improved integration of the Enterprise Java stack, Web Beans also provides a state of the art typesafe, stateful dependency injection framework. This is useful in a wide range of application types, enterprise or otherwise. To facilitate this, Web Beans provides a simple means for executing in the Java Standard Edition environment independently of any Enterprise Edition features.

When executing in the SE environment the following features of Web Beans are available:

To make life easy for developers Web Beans provides a special module with a main method which will boot the Web Beans manager, automatically registering all simple Web Beans found on the classpath. This eliminates the need for application developers to write any bootstrapping code. The entry point for a Web Beans SE applications is a simple Web Bean which observes the standard @Deployed Manager event. The command line paramters can be injected using either of the following:

@Parameters List<String

> params;
@Parameters String[] paramsArray; // useful for compatability with existing classes

Here's an example of a simple Web Beans SE application:

@ApplicationScoped

public class HelloWorld
{
    @Parameters List<String
> parameters;
    public void printHello( @Observes @Deployed Manager manager )
    {
        System.out.println( "Hello " + parameters.get(0) );
    }
}

Web Beans SE applications are started by running the following main method.

java org.jboss.webbeans.environments.se.StartMain <args

>

If you need to do any custom initialization of the Web Beans manager, for example registering custom contexts or initializing resources for your beans you can do so in response to the @Initialized Manager event. The following example registers a custom context:

public class PerformSetup

{
    public void setup( @Observes @Initialized Manager manager )
    {
        manager.addContext( ThreadContext.INSTANCE );
    }
}

目前,Web Bean的参考实现只能运行在JBoss AS5中;将参考实现整合到其他EE环境中(例如像Glassfish的其他的应用服务器)以及一个Servlet容器(像Tomcat)中或者一个内嵌的EJB3.1实现中相当容易。在附录中我们将简要的讨论所需的步骤。

The Web Beans SPI is located in webbeans-spi module, and packaged as webbeans-spi.jar. Some SPIs are optional, if you need to override the default behavior, others are required.

所有的SPI接口都支持装饰器模式,并且提供一个 Forwarding 类。

Web Bean参考实现也将EJB3 Bean的发现委托给容器,以便它不用再扫描EJB3注释和解析 ejb-jar.xml 文件。对于应用中的每个EJB都应发现一个EJBDescriptor:

public interface EjbServices

{
   
   /**
    * Gets a descriptor for each EJB in the application
    * 
    * @return The bean class to descriptor map 
    */
   public Iterable<EjbDescriptor<?>> discoverEjbs();
public interface EjbDescriptor<T

> {
   
   /**
    * Gets the EJB type
    * 
    * @return The EJB Bean class
    */
   public Class<T
> getType();
   /**
    * Gets the local business interfaces of the EJB
    * 
    * @return An iterator over the local business interfaces
    */
   public Iterable<BusinessInterfaceDescriptor<?>
> getLocalBusinessInterfaces();
   
   /**
    * Gets the remote business interfaces of the EJB
    * 
    * @return An iterator over the remote business interfaces
    */
   public Iterable<BusinessInterfaceDescriptor<?>
> getRemoteBusinessInterfaces();
   
   /**
    * Get the remove methods of the EJB
    * 
    * @return An iterator over the remove methods
    */
   public Iterable<Method
> getRemoveMethods();
   /**
    * Indicates if the bean is stateless
    * 
    * @return True if stateless, false otherwise
    */
   public boolean isStateless();
   /**
    * Indicates if the bean is a EJB 3.1 Singleton
    * 
    * @return True if the bean is a singleton, false otherwise
    */
   public boolean isSingleton();
   /**
    * Indicates if the EJB is stateful
    * 
    * @return True if the bean is stateful, false otherwise
    */
   public boolean isStateful();
   /**
    * Indicates if the EJB is and MDB
    * 
    * @return True if the bean is an MDB, false otherwise
    */
   public boolean isMessageDriven();
   /**
    * Gets the EJB name
    * 
    * @return The name
    */
   public String getEjbName();
   
   
}

EjbDescriptor 具有相当的自解释性,应该返回EJB规范中定义的相关元数据。除了这两个接口,还有一个表示本地业务接口的 BusinessInterfaceDescriptor (封装了接口类和用于查询EJB实例的jndi名字)

The resolution of @EJB and @Resource is delegated to the container. You must provide an implementation of org.jboss.webbeans.ejb.spi.EjbServices which provides these operations. Web Beans passes in the javax.inject.manager.InjectionPoint the resolution is for, as well as the NamingContext in use for each resolution request.

Web Bean RI必须将JTA活动委托给容器。SPI提供者提供一些钩子(hooks)结合TransactionServices 接口来轻松完成这个任务。

public interface TransactionServices

{
   /**
    * Possible status conditions for a transaction. This can be used by SPI
    * providers to keep track for which status an observer is used.
    */
   public static enum Status
   {
      ALL, SUCCESS, FAILURE
   }
   /**
    * Registers a synchronization object with the currently executing
    * transaction.
    * 
    * @see javax.transaction.Synchronization
    * @param synchronizedObserver
    */
   public void registerSynchronization(Synchronization synchronizedObserver);
   /**
    * Queries the status of the current execution to see if a transaction is
    * currently active.
    * 
    * @return true if a transaction is active
    */
   public boolean isTransactionActive();
}

枚举 Status 对实现者来说是一个很方便的工具,可以追踪在事务成功或者失败或者无论事务成功还是失败的情况下,是否应该将一个同步通知给观察者。

任何 javax.transaction.Synchronization 实现必须传递给 registerSynchronization() 方法,SPI实现应该立刻将同步注册到EJB使用的JTA事务管理器。

为了更容易的决定对于请求线程来说一个事务在当前是否是活动的,我们可以使用isTransactionActive() 方法。SPI实现应该查询EJB使用的同一个JTA事务管理器。