Help

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

9 comments:
 
24. Sep 2007, 22:35 CET | Link

Being a web-centric entity, it seems that RequestScoped would make the most sense. Will you discuss the pros and cons of @RequestScoped and @Dependent as defaults in the near future?

ReplyQuote
25. Sep 2007, 01:38 CET | Link

how do i demarcate the conversation context/scope, i.e. how do i say (e.g., in JSF) that this operation (e.g., pressing the launch wizard button) initiates a new conversation. and how do i distinguish between concurrent conversations? i suppose they must be named...

regards, gerald

 
25. Sep 2007, 03:48 CET | Link
Being a web-centric entity, it seems that RequestScoped would make the most sense. Will you discuss the pros and cons of @RequestScoped and @Dependent as defaults in the near future?

I'll try to write something up :-)

 
25. Sep 2007, 04:09 CET | Link
how do i demarcate the conversation contextscope, i.e. how do i say (e.g., in JSF) that this operation (e.g., pressing the launch wizard button) initiates a new conversation. and how do i distinguish between concurrent conversations? i suppose they must be named... regards, gerald

This would take me a whole new post to describe, and we've not yet finished defining it fully ;-)

 
25. Sep 2007, 04:25 CET | Link
Jimmy

Please, by all means, make this stuff available for plain Java (JSE) too.

All this (dependency resolution, scopes, ...) is highly useful for normal - e.g. swing - apps and shouldn't rely on neither a JEE container nor some kind of web server.

So

 
28. Sep 2007, 22:05 CET | Link
Shawn

Cool!

But one question, how to handle this situation @ApplicationScoped class Foo{ @SessionScoped User currentUser; } ? I don't think that @New would work here, because we need inject something existing in session instead of a brand-new instance.

Would the web bean container support this kind of injection ?

 
03. Oct 2007, 01:42 CET | Link
Would the web bean container support this kind of injection ?

Yes, of course cross-context injection is supported. That's one of the basic goals of the model.

 
04. Oct 2007, 05:22 CET | Link
msznapka

I am Seam newbie developer and I am missing some interesting things:

1).Injection inside method:

private void doSomething() {

log.debug(injecting inside method);

@In SomeComponent sc;

log.debug(Here it is: #0, sc);

}

it is even possible? ;)

2).Multiple running conversations without write restrictions

 
27. Oct 2007, 05:49 CET | Link
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.

Have you thought about making this default a property of a higher level typing (ScopeType, Stereotype)?

For example, how did you choose that a Servlet had request, application and session scopes available to it? Is there some concrete type that defines this? It would be great if this ServletContextType (Stereotype?) type was a first class citizen. I would then be able to define or override my default scope as well as add user defined scopes to the default search list. For example add my CustomJCAAware scope so that I have access to that scope within a Servlet.

It appears clear that developers will attempt to use Web Beans for non-web applications. I realize that defining these semantics aren't within the scope of the current JSR. However if all unscoped components defaulted to @RequestScoped this implementation detail may cause problems for those attempting to use a Web Beans container outside of a JEE container or even within a JEE container, but for non-web requests (like EJB3 over RMI or web services).

Pushing this default to the ScopeType or higher allows the debate over web-centric Web Beans defaults to continue but doesn't box the benefits of general contextual components to the web tier. I'd then allow for layering and inheritance of these properties, most specific wins.

ContextType defines scope search list and defines the context's default scope. ScopeType's may override the ContextType's default scope so that components without defined scope being injected into a existing scoped component can be scoped based on that scoped component's ScopeType definition.

Absent further discussion or additional features and complexity, I'd suggest defaulting to @Dependent as it's the most generic and least descriptive option. Further descriptions can then be layered and defaulted at higher levels.

Post Comment