SeamFramework.orgCommunity Documentation
Producer methods let us overcome certain limitations that arise when the Web Bean manager, instead of the application, is responsible for instantiating objects. They're also the easiest way to integrate objects which are not Web Beans into the Web Beans environment. (We'll meet a second approach in Chapter 12, Defining Web Beans using XML.)
According to the spec:
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.
.The scope of the producer method defaults to @Dependent
, and so
it will be called every time the Web Bean manager injects this field
or any other field that resolves to the same producer method. Thus, there could be
multiple instances of the PaymentStrategy
object for each user session.
To change this behavior, we can add a @SessionScoped
annotation
to the method.
@Produces @Preferred @SessionScoped
public PaymentStrategy getPaymentStrategy() {
...
}
Now, when the producer method is called, the returned PaymentStrategy
will be bound to the session context. The producer method won't be called again in the same
session.
There's one potential problem with the code above. The implementations of
CreditCardPaymentStrategy
are instantiated using the Java
new
operator. Objects instantiated directly by the application
can't take advantage of dependency injection and don't have interceptors.
If this isn't what we want we can use dependency injection into the producer method to obtain Web Bean instances:
@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;
}
}
Wait, what if CreditCardPaymentStrategy
is a request scoped
Web Bean? Then the producer method has the effect of "promoting" the current request
scoped instance into session scope. This is almost certainly a bug! The request
scoped object will be destroyed by the Web Bean manager before the session ends, but
the reference to the object will be left "hanging" in the session scope. This error
will not be detected by the Web Bean manager, so please take
extra care when returning Web Bean instances from producer methods!
There's at least three ways we could go about fixing this bug. We could change
the scope of the CreditCardPaymentStrategy
implementation, but this
would affect other clients of that Web Bean. A better option would be to change the
scope of the producer method to @Dependent
or
@RequestScoped
.
But a more common solution is to use the special @New
binding
annotation.
Consider the following producer method:
@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;
}
}
Then a new dependent instance of
CreditCardPaymentStrategy
will be created, passed to the producer
method, returned by the producer method and finally bound to the session context. The
dependent object won't be destroyed until the Preferences
object is
destroyed, at the end of the session.