SeamFramework.orgCommunity Documentation

第 6 章 生产者方法

6.1. 生产者方法的范围
6.2. 注入到生产者方法中
6.3. 在生产者方法中使用 @New

生产者方法能够让我们克服使用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:

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.

.

默认的生产者方法的范围是 @Dependent,所以Web Bean管理器每次注入这个域或者注入任何其他对应改生产者方法的域的时候,这个生产者方法都会被调用。这样,对于每个用户会话,我们有可能有多个PaymentStrategy实例。

要改变这种行为,我们可以给这个方法添加一个 @SessionScoped 注释。

@Produces @Preferred @SessionScoped

public PaymentStrategy getPaymentStrategy() {
    ...
}

现在,当这个生产者方法被调用时,它返回的 PaymentStrategy 对象将被绑定到会话上下文中。这个生产者方法在同一个会话范围中不会被再次调用。

上面的代码有一个潜在的问题。 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 绑定注释。

考虑到下面的生产者方法:

@Produces @Preferred @SessionScoped

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

一个新的依赖CreditCardPaymentStrategy 实例将被创建,然后传递给生产者方法,然后生产者方法将其返回,最终这个实例将被绑定到会话上下文。这个依赖对象不会被提前销毁,而是在会话结束时跟随 Preferences 一起被销毁。