Chapter 3. Dependency injection

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.

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 8, 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:

Let's explore how the Web Beans manager determines a Web Bean to be injected.

3.1. Binding annotations

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;
}

3.1.1. Binding annotations with members

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.

3.1.2. Combinations of binding annnotations

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.

3.1.3. Binding annotations and producer methods

Even producer methods may specify binding annotations:

@Produces 
@Asynchronous @PayByCheque 
PaymentProcessor createAsyncPaymentProcessor(@PayByCheque PaymentProcessor processor) {
    return new AsynchronousPaymentProcessor(processor);
}

3.1.4. The default binding type

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.

3.2. Deployment types

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?

3.2.1. Enabling deployment types

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.

3.2.2. Deployment type precedence

If you've been paying attention, you're probably wondering how the Web Bean manager decides which implementation—ExternalPaymentProcessor 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.

3.2.3. Example deployment types

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...

3.3. Fixing unsatisfied dependencies

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 point—or 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.

3.4. Client proxies

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.

3.5. Obtaining a Web Bean by programatic lookup

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; } 
                                               } );

3.6. Lifecycle callbacks, @Resource, @EJB and @PersistenceContext

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.