/This is the fourth installment of a series of articles describing the current status of the Web Beans specification. Please read the first, second and third installments first./

So far, we've seen a few examples of /scope type annotations/. The scope of a component determines the lifecycle of the component instances, and makes a particular instance visible to all components executing in a particular context.

For example, if we have a session scoped component, CurrentUser, all components that are called in the context of the same HttpSession will see the same instance of CurrentUser. This instance will be automatically created the first time a CurrentUser is needed in that session, and automatically destroyed when the session ends.

Scope types

Web Beans features an /extensible context model/. It is possible to define new scopes by creating a new scope type annotation:

@Retention(RUNTIME)
@Target({TYPE, METHOD})
@ScopeType
public @interface ClusterScoped {}

Of course, that's the easy part of the job. For this scope type to be useful, we will also need to define a Context object that implements the scope! Implementing a Context is usually a very technical task, intended for framework development only.

We can apply a scope type annotation to a Web Bean implementation class to specify the scope of the component:

@ClusterScoped @Component
public class SecondLevelCache { ... }

We can even use the scope type to obtain an instance of the Context object for the scope:

Component<SecondLevelCache> cacheComponent = container.resolveByType(SecondLevelCache.class);
SecondLevelCache cache = container.getContext(ClusterScoped.class).get(cacheComponent);

Built-in scopes

Web Beans defines four built-in scopes:

  • @RequestScoped
  • @SessionScoped
  • @ApplicationScoped
  • @ConversationScoped

For a web application that uses Web Beans:

  1. any servlet has access to active request, session and application contexts
  2. any JSF request has access to an active conversation context

If the application tries to use a component with a scope that does not have an active context, a ContextNotActive exception is thrown by the Web Beans container at runtime.

The dependent psuedo-scope

In addition, there is the notion of the /dependent psuedo-scope/. We use the term psuedo-scope because there is no Context for this special scope.

A Web Bean may be declared to be a @Dependent component:

@Dependent @Component
public class Calculator { ... }

If this case, a new instance of the component is created each time the Web Beans container injects it. This means that any instance of a dependent component is bound to the object into which it was injected. Different clients always see different instances of a dependent component, no matter what context they execute in.

A open issue that currently exists in the Web Beans specification is the question of the default scope for a Web Bean component that does not explicitly declare a scope type. We've narrowed the options down to @RequestScoped and @Dependent, each of which has advantages and disadvantages.

Implicit dependent components

The built-in @New binding annotation allows /implicit/ definition of a dependent component at an injection point. Suppose we declare the following injected attribute:

@In @New Calculator calculator

Then a component with component type @Component, scope @Dependent, binding annotation @New, API type Calculator and implementation class Calculator is implicitly defined.

This is true even if Calculator is /already/ declared as a Web Beans component, for example:

@ConversationScoped @Component
public class Calculator { ... }

So the following injected attributes each get a different instance of Calculator:

@Component 
public class PaymentCalc {

    @In Calculator calculator;
    @In @New Calculator newCalculator;

}

The calculator field has a conversation-scoped instance of Calculator injected. The newCalculator field has a new instance of Calculator injected, with a lifecycle that is bound to the owning PaymentCalc.

This feature is particularly useful with resolver methods.

Resolver methods

According to the spec:

A Web Beans resolver 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 components,
  • 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, resolver methods let us:

  • expose a JPA entity as a Web Bean component
  • expose a JDK class as a Web Bean component
  • define multiple Web Bean components, with different scopes or initialization, for the same implementation class
  • vary a Web Bean component implementation class at runtime

In particular, resolver methods let us use runtime polymorphism with Web Beans. As we've seen, component types are a powerful solution to the problem of deployment-time polymorphism. But once the system is deployed, the component implementation is fixed. A resolver method has no such limitation:

@SessionScoped @Component
public class Preferences {
    
    private PaymentStrategyType paymentStrategy;
    
    ...
    
    @Resolves @Preferred @ApplicationScoped
    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 this injection point:

@In @Preferred PaymentStrategy paymentStrat;

When the container injects this field, the resolver method will be called and the returned PaymentStrategy will be injected into the field and bound to the application context. The resolver method won't be called again in the same application context. On the other hand, if we were to remove the @ApplicationScoped annotation:

@Resolves @Preferred
public PaymentStrategy getPaymentStrategy() {
    ...
}

Then the resolver method defaults to dependent scope, and it will be called /every time/ the field is injected!

Injection into resolver methods

There's one problem with this code. If CreditCardPaymentStrategy is a Web Bean component, it should be created by the Web Beans container, not by calling new. We can solve this problem by using injection into the resolver method:

@Resolves @Preferred @ApplicationScoped
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 component? Then the resolver method has the effect of promoting the current request scoped instance into application scope. This is almost certainly a bug. We can fix the bug using the special @New binding annotation described above:

@Resolves @Preferred @ApplicationScoped
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 resolver method, returned by the resolver and finally bound to the application context.


Back to top