SeamFramework.orgCommunity Documentation
Web Beans supports three primary mechanisms for dependency injection:
Constructor parameter injection:
public class Checkout {
private final ShoppingCart cart;
@Initializer
public Checkout(ShoppingCart cart) {
this.cart = cart;
}
}
Initializer method parameter injection:
public class Checkout {
private ShoppingCart cart;
@Initializer
void setShoppingCart(ShoppingCart cart) {
this.cart = cart;
}
}
And direct field injection:
public class Checkout {
private @Current ShoppingCart cart;
}
Dependency injection always occurs when the Web Bean instance is first instantiated.
First, the Web Bean manager calls the Web Bean constructor, to obtain an instance of the Web Bean.
Next, the Web Bean manager initializes the values of all injected fields of the Web Bean.
Next, the Web Bean manager calls all initializer methods of Web Bean.
Finally, the @PostConstruct
method of the Web
Bean, if any, is called.
Constructor parameter injection is not supported for EJB beans, since the EJB is instantiated by the EJB container, not the Web Bean manager.
Parameters of constructors and initializer methods need not be explicitly
annotated when the default binding type @Current
applies.
Injected fields, however, must specify a binding type, even
when the default binding type applies. If the field does not specify a binding
type, it will not be injected.
Producer methods also support parameter injection:
@Produces Checkout createCheckout(ShoppingCart cart) {
return new Checkout(cart);
}
Finally, observer methods (which we'll meet in Chapter 9, Events), disposal methods and destructor methods all support parameter injection.
The Web Beans specification defines a procedure, called the typesafe
resolution algorithm, that the Web Bean manager follows when identifying
the Web Bean to inject to an injection point. This algorithm looks complex at first,
but once you understand it, it's really quite intuitive. Typesafe resolution is
performed at system initialization time, which means that the manager will inform
the user immediately if a Web Bean's dependencies cannot be satisfied, by throwing
a UnsatisfiedDependencyException
or
AmbiguousDependencyException
.
The purpose of this algorithm is to allow multiple Web Beans to implement the same API type and either:
allow the client to select which implementation it requires using binding annotations,
allow the application deployer to select which implementation is appropriate for a particular deployment, without changes to the client, by enabling or disabling deployment types, or
allow one implementation of an API to override another implementation of the same API at deployment time, without changes to the client, using deployment type precedence.
Let's explore how the Web Beans manager determines a Web Bean to be injected.
If we have more than one Web Bean that implements a particular API type, the
injection point can specify exactly which Web Bean should be injected using a binding
annotation. For example, there might be two implementations of
PaymentProcessor
:
@PayByCheque
public class ChequePaymentProcessor implements PaymentProcessor {
public void process(Payment payment) { ... }
}
@PayByCreditCard
public class CreditCardPaymentProcessor implements PaymentProcessor {
public void process(Payment payment) { ... }
}
Where @PayByCheque
and @PayByCreditCard
are binding annotations:
@Retention(RUNTIME)
@Target({TYPE, METHOD, FIELD, PARAMETER})
@BindingType
public @interface PayByCheque {}
@Retention(RUNTIME)
@Target({TYPE, METHOD, FIELD, PARAMETER})
@BindingType
public @interface PayByCreditCard {}
A client Web Bean developer uses the binding annotation to specify exactly which Web Bean should be injected.
Using field injection:
@PayByCheque PaymentProcessor chequePaymentProcessor;
@PayByCreditCard PaymentProcessor creditCardPaymentProcessor;
Using initializer method injection:
@Initializer
public void setPaymentProcessors(@PayByCheque PaymentProcessor chequePaymentProcessor,
@PayByCreditCard PaymentProcessor creditCardPaymentProcessor) {
this.chequePaymentProcessor = chequePaymentProcessor;
this.creditCardPaymentProcessor = creditCardPaymentProcessor;
}
Or using constructor injection:
@Initializer
public Checkout(@PayByCheque PaymentProcessor chequePaymentProcessor,
@PayByCreditCard PaymentProcessor creditCardPaymentProcessor) {
this.chequePaymentProcessor = chequePaymentProcessor;
this.creditCardPaymentProcessor = creditCardPaymentProcessor;
}
Binding annotations may have members:
@Retention(RUNTIME)
@Target({TYPE, METHOD, FIELD, PARAMETER})
@BindingType
public @interface PayBy {
PaymentType value();
}
In which case, the member value is significant:
@PayBy(CHEQUE) PaymentProcessor chequePaymentProcessor;
@PayBy(CREDIT_CARD) PaymentProcessor creditCardPaymentProcessor;
You can tell the Web Bean manager to ignore a member of a binding annotation
type by annotating the member @NonBinding
.
An injection point may even specify multiple binding annotations:
@Asynchronous @PayByCheque PaymentProcessor paymentProcessor
In this case, only a Web Bean which has both binding annotations would be eligible for injection.
Even producer methods may specify binding annotations:
@Produces
@Asynchronous @PayByCheque
PaymentProcessor createAsyncPaymentProcessor(@PayByCheque PaymentProcessor processor) {
return new AsynchronousPaymentProcessor(processor);
}
Web Beans defines a binding type @Current
that is the
default binding type for any injection point or Web Bean that does not explicitly
specify a binding type.
There are two common circumstances in which it is necessary to explicitly
specify @Current
:
on a field, in order to declare it as an injected field with the default binding type, and
on a Web Bean which has another binding type in addition to the default binding type.
All Web Beans have a deployment type. Each deployment type identifies a set of Web Beans that should be conditionally installed in some deployments of the system.
For example, we could define a deployment type named @Mock
,
which would identify Web Beans that should only be installed when the system executes
inside an integration testing environment:
@Retention(RUNTIME)
@Target({TYPE, METHOD})
@DeploymentType
public @interface Mock {}
Suppose we had some Web Bean that interacted with an external system to process payments:
public class ExternalPaymentProcessor {
public void process(Payment p) {
...
}
}
Since this Web Bean does not explicitly specify a deployment type, it has the
default deployment type @Production
.
For integration or unit testing, the external system is slow or unavailable. So we would create a mock object:
@Mock
public class MockPaymentProcessor implements PaymentProcessor {
@Override
public void process(Payment p) {
p.setSuccessful(true);
}
}
But how does the Web Bean manager determine which implementation to use in a particular deployment?
Web Beans defines two built-in deployment types: @Production
and @Standard
. By default, only Web Beans with the built-in deployment
types are enabled when the system is deployed. We can identify additional deployment
types to be enabled in a particular deployment by listing them in
web-beans.xml
.
Going back to our example, when we deploy our integration tests, we want all
our @Mock
objects to be installed:
<WebBeans>
<Deploy>
<Standard/>
<Production/>
<test:Mock/>
</Deploy>
</WebBeans>
Now the Web Bean manager will identify and install all Web Beans annotated
@Production
, @Standard
or @Mock
at deployment time.
The deployment type @Standard
is used only for certain
special Web Beans defined by the Web Beans specification. We can't use it for
our own Web Beans, and we can't disable it.
The deployment type @Production
is the default deployment
type for Web Beans which don't explicitly declare a deployment type, and may be
disabled.
If you've been paying attention, you're probably wondering how the Web Bean
manager decides which implementationExternalPaymentProcessor
or MockPaymentProcessor
to choose. Consider what happens when
the manager encounters this injection point:
@Current PaymentProcessor paymentProcessor
There are now two Web Beans which satisfy the PaymentProcessor
contract. Of course, we can't use a binding annotation to disambiguate, since binding
annotations are hard-coded into the source at the injection point, and we want the
manager to be able to decide at deployment time!
The solution to this problem is that each deployment type has a different
precedence. The precedence of the deployment types is determined
by the order in which they appear in web-beans.xml
. In our example,
@Mock
appears later than @Production
so it
has a higher precedence.
Whenever the manager discovers that more than one Web Bean could satisfy the
contract (API type plus binding annotations) specified by an injection point, it
considers the relative precedence of the Web Beans. If one has a higher precedence
than the others, it chooses the higher precedence Web Bean to inject. So, in our example,
the Web Bean manager will inject MockPaymentProcessor
when executing
in our integration testing environment (which is exactly what we want).
It's interesting to compare this facility to today's popular manager architectures. Various "lightweight" containers also allow conditional deployment of classes that exist in the classpath, but the classes that are to be deployed must be explicity, individually, listed in configuration code or in some XML configuration file. Web Beans does support Web Bean definition and configuration via XML, but in the common case where no complex configuration is required, deployment types allow a whole set of Web Beans to be enabled with a single line of XML. Meanwhile, a developer browsing the code can easily identify what deployment scenarios the Web Bean will be used in.
Deployment types are useful for all kinds of things, here's some examples:
@Mock
and @Staging
deployment types
for testing
@AustralianTaxLaw
for site-specific Web Beans
@SeamFramework
, @Guice
for third-party
frameworks which build on Web Beans
@Standard
for standard Web Beans defined by the Web Beans
specification
I'm sure you can think of more applications...
The typesafe resolution algorithm fails when, after considering the binding annotations and and deployment types of all Web Beans that implement the API type of an injection point, the Web Bean manager is unable to identify exactly one Web Bean to inject.
It's usually easy to fix an UnsatisfiedDependencyException
or
AmbiguousDependencyException
.
To fix an UnsatisfiedDependencyException
, simply provide
a Web Bean which implements the API type and has the binding types of the injection
pointor enable the deployment type of a Web Bean that already implements the
API type and has the binding types.
To fix an AmbiguousDependencyException
, introduce a
binding type to distinguish between the two implementations of the API type,
or change the deployment type of one of the implementations so that the Web
Bean manager can use deployment type precedence to choose between them. An
AmbiguousDependencyException
can only occur if two Web Beans
share a binding type and have exactly the same deployment type.
There's one more issue you need to be aware of when using dependency injection in Web Beans.
Clients of an injected Web Bean do not usually hold a direct reference to a Web Bean instance.
Imagine that a Web Bean bound to the application scope held a direct reference to a Web Bean bound to the request scope. The application scoped Web Bean is shared between many different requests. However, each request should see a different instance of the request scoped Web bean!
Now imagine that a Web Bean bound to the session scope held a direct reference to a Web Bean bound to the application scope. From time to time, the session context is serialized to disk in order to use memory more efficiently. However, the application scoped Web Bean instance should not be serialized along with the session scoped Web Bean!
Therefore, unless a Web Bean has the default scope @Dependent
,
the Web Bean manager must indirect all injected references to the Web Bean through a
proxy object. This client proxy is responsible for ensuring that
the Web Bean instance that receives a method invocation is the instance that is
associated with the current context. The client proxy also allows Web Beans bound
to contexts such as the session context to be serialized to disk without recursively
serializing other injected Web Beans.
Unfortunately, due to limitations of the Java language, some Java types cannot
be proxied by the Web Bean manager. Therefore, the Web Bean manager throws an
UnproxyableDependencyException
if the type of an injection point
cannot be proxied.
The following Java types cannot be proxied by the Web Bean manager:
classes which are declared final
or have a
final
method,
classes which have no non-private constructor with no parameters, and
arrays and primitive types.
It's usually very easy to fix an UnproxyableDependencyException
.
Simply add a constructor with no parameters to the injected class, introduce an interface,
or change the scope of the injected Web Bean to @Dependent
.
The application may obtain an instance of the interface Manager
by injection:
@Current Manager manager;
The Manager
object provides a set of methods for obtaining a
Web Bean instance programatically.
PaymentProcessor p = manager.getInstanceByType(PaymentProcessor.class);
Binding annotations may be specified by subclassing the helper class
AnnotationLiteral
, since it is otherwise difficult to instantiate an
annotation type in Java.
PaymentProcessor p = manager.getInstanceByType(PaymentProcessor.class,
new AnnotationLiteral<CreditCard>(){});
If the binding type has an annotation member, we can't use an anonymous subclass of
AnnotationLiteral
instead we'll need to create a named subclass:
abstract class CreditCardBinding
extends AnnotationLiteral<CreditCard>
implements CreditCard {}
PaymentProcessor p = manager.getInstanceByType(PaymentProcessor.class,
new CreditCardBinding() {
public void value() { return paymentType; }
} );
Enterprise Web Beans support all the lifecycle callbacks defined by the EJB
specification: @PostConstruct
, @PreDestroy
,
@PrePassivate
and @PostActivate
.
Simple Web Beans support only the @PostConstruct
and
@PreDestroy
callbacks.
Both enterprise and simple Web Beans support the use of @Resource
,
@EJB
and @PersistenceContext
for injection of Java
EE resources, EJBs and JPA persistence contexts, respectively. Simple Web Beans do not
support the use of @PersistenceContext(type=EXTENDED)
.
The @PostConstruct
callback always occurs after all dependencies
have been injected.
There are certain kinds of dependent objectsWeb Beans with scope
@Dependent
that need to know something about the object or injection
point into which they are injected in order to be able to do what they do. For example:
The log category for a Logger
depends upon the class of the object
that owns it.
Injection of a HTTP parameter or header value depends upon what parameter or header name was specified at the injection point.
Injection of the result of an EL expression evaluation depends upon the expression that was specified at the injection point.
A Web Bean with scope @Dependent
may inject an instance of
InjectionPoint
and access metadata relating to the injection point to which
it belongs.
Let's look at an example. The following code is verbose, and vulnerable to refactoring problems:
Logger log = Logger.getLogger(MyClass.class.getName());
This clever little producer method lets you inject a JDK Logger
without
explicitly specifying the log category:
class LogFactory {
@Produces Logger createLogger(InjectionPoint injectionPoint) {
return Logger.getLogger(injectionPoint.getMember().getDeclaringClass().getName());
}
}
We can now write:
@Current Logger log;
Not convinced? Then here's a second example. To inject HTTP parameters, we need to define a binding type:
@BindingType
@Retention(RUNTIME)
@Target({TYPE, METHOD, FIELD, PARAMETER})
public @interface HttpParam {
@NonBinding public String value();
}
We would use this binding type at injection points as follows:
@HttpParam("username") String username;
@HttpParam("password") String password;
The following producer method does the work:
class HttpParams
@Produces @HttpParam("")
String getParamValue(ServletRequest request, InjectionPoint ip) {
return request.getParameter(ip.getAnnotation(HttpParam.class).value());
}
}
(Note that the value()
member of the HttpParam
annotation is ignored by the Web Bean manager since it is annotated @NonBinding.
)
The Web Bean manager provides a built-in Web Bean that implements the
InjectionPoint
interface:
public interface InjectionPoint {
public Object getInstance();
public Bean<?> getBean();
public Member getMember():
public <T extends Annotation> T getAnnotation(Class<T> annotation);
public Set<T extends Annotation> getAnnotations();
}