/This is the second installment of a series of articles describing the current status of the Web Beans specification. You can find the first installment here./

Web Beans supports three primary mechanisms for dependency injection:

Direct field injection:

@Component
public class Checkout {

    @In ShoppingCart cart;
    
}

Method injection:

@Component
public class Checkout {
        
    private ShoppingCart cart;

    @In void setShoppingCart(ShoppingCart cart) {
        this.cart = cart;
    }
    
}

And constructor injection:

@Component
public class Checkout {
        
    private final ShoppingCart cart;

    public Checkout(ShoppingCart cart) {
        this.cart = cart;
    }

}

In addition, resolver methods support parameter injection:

@Resolves Checkout createCheckout(ShoppingCart cart) {
    return new Checkout(cart);
}

Dependency injection always occurs when the component instance is first instantiated.

The Web Beans specification defines a procedure, called the /typesafe resolution algorithm/, that the Web Beans container follows when identifying the component 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 container will inform the user immediately if a component's dependencies cannot be satisfied.

The purpose of this algorithm is to allow multiple components to implement the same API type and either:

  • allow the client to select which implementation it requires using /binding annotations/, or
  • allow one implementation of an API to override another implementation of the same API at deployment time, without changes to the client, using /component type precedence/.

Let's explore how the Web Beans container determines a component to be injected.

Binding annotations

If we have more than one component that implements a particular API type, the injection point can specify exactly which component should be injected using a binding annotation. For example, there might be two implementations of PaymentProcessor:

@Component @PayByCheque
public class ChequePaymentProcessor implements PaymentProcessor {
    public void process(Payment payment) { ... }
}

@Component @PayByCreditCard
public class CreditCardPaymentProcessor implements PaymentProcessor {
    public void process(Payment payment) { ... }
}

Where @PayByCheque and @PayByCreditCard are binding annotations:

@Retention(RUNTIME)
@Target({TYPE, METHOD, FIELD})
@BindingType
public @interface PayByCheque {}
@Retention(RUNTIME)
@Target({TYPE, METHOD, FIELD})
@BindingType
public @interface PayByCreditCard {}

A client component developer uses the binding annotation to specify exactly which component should be injected:

@In @PayByCheque PaymentProcessor chequePaymentProcessor;
@In @PayByCreditCard PaymentProcessor creditCardPaymentProcessor;

Equivalently, using constructor injection:

public Checkout(@PayByCheque PaymentProcessor chequePaymentProcessor, 
                 @PayByCreditCard PaymentProcessor creditCardPaymentProcessor) {
   this.chequePaymentProcessor = chequePaymentProcessor;
   this.creditCardPaymentProcessor = creditCardPaymentProcessor;
}

Binding annotations with members

Binding annotations may have members:

@Retention(RUNTIME)
@Target({TYPE, METHOD, FIELD})
@BindingType
public @interface PayBy {
    PaymentType value();
}

In which case, the member value is significant:

@In @PayBy(CHEQUE) PaymentProcessor chequePaymentProcessor;
@In @PayBy(CREDIT_CARD) PaymentProcessor creditCardPaymentProcessor;

Combinations of binding annnotations

An injection point may even specify multiple binding annotations:

@In @Asynchronous @PayByCheque paymentProcessor

In this case, only a component which supports /both/ binding annotation would be eligable for injection.

Binding annotations and resolver methods

Of course, even resolver methods may specify binding annotations:

@Resolves 
@Asynchronous @PayByCheque 
PaymentProcessorService createAsyncPaymentProcessor(@PayByCheque PaymentProcessor processor) {
    return new AsynchronousPaymentProcessorService(processor);
}

This method would be called to create an instance for injection to the following injection point:

@In @Asynchronous @PayByCheque PaymentProcessorService service;

Component types

All Web Bean components have a /component type/. Each component type identifies a set of components that should be conditionally installed in some deployments of the system.

For example, we could define a component type named @Mock, which would identify components that should only be installed when the system executes inside an integration testing environment:

@Retention(RUNTIME)
@Target({TYPE, METHOD})
@ComponentType
public @interface Mock {}

Suppose we had some component that interacted with an external system to process payments:

@Component
public class PaymentProcessor {
        
    public void process(Payment p) {
        ...
    }
    
}

For integration or unit testing, the external system is slow or unavailable. So we would create a mock object:

@Mock 
public class MockPaymentProcessor extends PaymentProcessor {

    @Override
    public void process(Payment p) {
        p.setSuccessful(true);
    }

}

Installing component types

Web Beans defines two built-in component types: @Component and @Standard. By default, only components with the built-in component types are installed when the system is deployed. We can identify additional component types to be installed 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:

<web-beans>
    <component-types>
        <component-type>javax.webbeans.Standard</component-type>
        <component-type>javax.webbeans.Component</component-type>
        <component-type>org.jboss.test.Mock</component-type>
    </component-types>
</web-beans>

Now the Web Beans container will identify and install all components annotated @Component, @Standard or @Mock at deployment time.

(Note that a component with no component type annotation will never be discovered by the Web Beans container; the container searches for classes with component type annotations when scanning the classpath to discover components.)

It's interesting to compare this facility to today's popular container architectures. Various lightweight containers also allow conditional deployment of components that exist in the classpath, but the components that are to be deployed must be explicity, individually, listed in configuration code or in some XML configuration file. Web Beans does support component definition and configuration via XML, but in the common case where no complex configuration is required, component types allow a whole set of components to be enabled with a single line of XML. Meanwhile, a developer browsing the code can easily identify what context the component will be used in.

Component type precedence

If you've been paying attention, you're probably wondering how the container decides which implementation - PaymentProcessor or MockPaymentProcessor - to choose. Consider what happens when the container encounters this injection point:

@In PaymentProcessor pp;

There are now two components which satisfy the PaymentProcessor contract. Of course, we can't use a binding annotation to disambiguate, since binding annotations are hardcoded into the source at the injection point, and we want the container to be able to decide at deployment time!

The solution to this problem is that each component type has a different /precedence/. The precedence of the component types is determined by the order in which they appear in web-beans.xml. In our example, @Mock appears later than @Component so it has a higher precedence.

Whenever the container discovers that more than one component could satisfy the contract (API type plus binding annotations) specified by an injection point, it considers the relative precedence of the components. If one has a higher precedence than the others, it chooses the higher precedence component to inject. So, in our example, the container will inject MockPaymentProcessor when executing in the integration testing environment, and PaymentProcessor when executing in production (which is exactly what we want).

Example component types

Component types are useful for all kinds of things, some more examples:

  • @Mock and @Staging component types for testing
  • @AustralianTaxLaw for site-specific components
  • @SeamFramework, @Guice for third-party frameworks which build on Web Beans
  • @Standard for standard components defined by the Web Beans specification

I'm sure you can think of more applications...

Two more rules to remember?

As we've seen, the typesafe resolution algorith considers:

  1. API type
  2. binding annotations
  3. component type precedence

when resolving a component to be injected. If these rules fail to produce a unique component (if there are multiple components which all have the same high precedence, implement the required API type and support the required binding annotations), an exception will be thrown by the container at system initialization time.

The Web Beans group has discussed the possibility of introducing two additional rules to be used only when the rules above fail to narrow the list of components to a unique component. On balance, I'm in favor of these rules, since I think they're useful. However, the group as a whole is concerned that the additional complexity is confusing to developers. Therefore, we're seeking feedback from the community on the following ideas.

/Note:/ If you're not used to thinking in terms of binding annotations and component precendence, you're probably thinking that component resolution is already complex enough! But I'm confident that once you get used to it, the resolution algorithm is extremely robust and will grow to become totally intuitive.

Rule 1: The /least-derived implementation/ rule

This proposed rule states that if the algorithm above fails to result in a unique component, and if there is a /least-derived/ component in the set of candidates - one candidate that the other candidates all (directly or indirectly) extend, then the least-derived component will be chosen.

This rule is designed to ensure that it is easy to introduce new components into the system using implementation inheritance from pre-existing components, without breaking or changing the behavior of pre-existing clients.

For example, if I have this pre-existing component:

@Component @PayByCheque
ChequePaymentProcessor implements PaymentProcessor { ... }

and I introduce a new component that extends this one:

@Component @PayByCheque
AsynchronousChequePaymentProcessor 
    extends ChequePaymentProcessor 
    implements AsynchronousPaymentProcessor { ... }

Then the following injection point would continue to resolve to the base ChequePaymentProcessor component:

@In @PayByCheque PaymentProcessor processor;

While this new injection point would of course receive an instance of AsynchronousChequePaymentProcessor:

@In @PayByCheque AsynchronousPaymentProcessor processor;

Rule 2: The /least-specific bindings/ rule

This proposed rule is analogous to the previous rule, but refers to binding annotations rather than API types. It could be adopted either in addition to, or as an alternative to, Rule 1. It states that if the algorithm above fails to result in a unique component, and if there is exactly one component in the set of candidates that has /exactly/ the same binding annotations specified at the injection point, then this least-specific component will be chosen.

For example, if I have this pre-existing component:

@Component @PayByCheque 
ChequePaymentProcessor implements PaymentProcessor { ... }

and I introduce a new component that also supports the @PayByCheque binding annotation:

@Component @PayByCheque @Asynchronous
AsynchronousChequePaymentProcessor 
    extends ChequePaymentProcessor { ... }

Then the following injection point would continue to resolve to the base ChequePaymentProcessor component:

@In @PayByCheque PaymentProcessor paymentProcessor;

While this new injection point would of course receive an instance of AsynchronousPaymentProcessor:

@In @PayByCheque @Asynchronous PaymentProcessor paymentProcessor;

Are you scared yet?

Phew, we started to get a bit esoteric there! Don't worry, this is as complex as it gets, and if we get too much negative feedback about the two proposed rules above, it won't even get /that/ complex.


Back to top