SeamFramework.orgCommunity Documentation

第 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端点

你是否已经迫不及待想要开始编写你的第一个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