SeamFramework.orgCommunity Documentation

Chapter 4. Dependency injection

4.1. Binding annotations
4.1.1. Binding annotations with members
4.1.2. Combinations of binding annnotations
4.1.3. Binding annotations and producer methods
4.1.4. The default binding type
4.2. Deployment types
4.2.1. Enabling deployment types
4.2.2. Deployment type precedence
4.2.3. Example deployment types
4.3. Fixing unsatisfied dependencies
4.4. Client proxies
4.5. Obtaining a Web Bean by programatic lookup
4.6. Lifecycle callbacks, @Resource, @EJB and @PersistenceContext
4.7. The InjectionPoint object

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

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

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?

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.

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.

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:

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 objects — Web 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:

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 <extends Annotation> T getAnnotation(Class<T> annotation); 
   public Set<extends Annotation> getAnnotations(); 
}