Scopes and Contexts - JBoss Weld CDI for Java Platform (2013)

JBoss Weld CDI for Java Platform (2013)

Chapter 4. Scopes and Contexts

We've already briefly covered scope annotations and how they work, but now we will discuss them in more detail, with emphasis on the conversation scope, followed by pseudo scopes in more detail, and lastly we will provide everything needed to create our own custom scopes.

Scope types

CDI defines two different scope types, normal scope and pseudo scope, which define how a scope will function within CDI. All the built-in scopes are specified with one of these scope types, and they can also be used to create our own scope, which we will see later in the chapter.

A normal scope is declared with @NormalScope to indicate to the container that a client proxy is required. It has a single attribute, which specifies whether the scope is passivating or not; that is, whether a bean that uses this scope is able to be passivated to secondary storage. @RequestScoped, @SessionScoped, @ApplicationScoped, and @ConversationScoped are all examples of scopes with a normal scope type.

A pseudo scope is declared with @Scope to indicate that no client proxy is required by the container. @Dependent is a scope with a pseudo scope type as instances are never proxied or shared.

Built-in scopes

The term built-in scope refers to the scope annotations that are defined by the CDI specification and implemented by Weld for us to use in our applications. They represent the most common scope use cases that are present within web application development in a servlet container. If it didn't provide these scope annotations for us, it would be extremely inefficient and error prone for us to implement our own solutions for these scopes.

The CDI specification defines the following built-in scopes, some of which we've already used in the previous chapters:

· @RequestScoped

· @SessionScoped

· @ApplicationScoped

· @ConversationScoped

@RequestScoped , @SessionScoped , and @ApplicationScoped are all applicable to any web application that we develop, and they are accessible from any servlet request within our application.

@ConversationScoped is specifically targeted for use with a JSF request by the CDI specification, though it is possible to implement support for other web frameworks by developing a CDI extension. It is available during all lifecycle phases of any JSF faces or a non-faces request. In the next section we will cover this scope annotation in further detail.

Note

CDI 1.1 will expand @ConversationScoped for use with all servlet requests and remove the need for it to be tied to JSF.

Due to CDI's integration with Java EE, the request and application scopes are also active during:

· EJB remote method invocations

· EJB asynchronous method invocations

· EJB timeouts

· Message delivery to a message-driven bean or MessageListener

· Web service invocations

Each built-in scope annotation has an associated context object, which determines the lifecycle and instance visibility of all beans defined within a given scope. Each built-in scope annotation also has an implementation of the Context interface provided by Weld. It is through the Context and Contextual interfaces that the context implementation works with the Weld container to create and destroy contextual instances.

Note

A contextual instance is an instance of a bean that was created within a specific context, which is synonymous with it being created in a specific scope.

The request context lifecycle

The request context is the context object for the @RequestScoped built-in scope annotation.

It follows the standard pattern of a web request and is activated during any of the following:

· The service() method of any servlet

· The doFilter() method of any servlet filter

· Calls from the servlet container to any ServletContextListener, HttpSessionListener, AsyncListener, or ServletRequestListener listener

· A Java EE web service call

· Asynchronous observer method notification

· EJB remote method invocation, EJB asynchronous method invocation, EJB timeout method call, and EJB message-driven bean message delivery

· Message delivery to a MessageListener for a JMS topic or queue, but only if it was obtained from the Java EE component environment

The request context is destroyed at the end of the servlet request and after each of the invocations described previously have completed.

The session context lifecycle

The session context is the context object for the @SessionScoped built-in scope annotation. This scope is known as a passivating scope because, depending on the needs of the container, it may need to be serialized to secondary storage for a time, and it needs to implement Serializable to support this behavior.

The session context is active during the following:

· The service() method of any servlet

· The doFilter() method of any servlet filter

· Calls from the servlet container to any HttpSessionListener, AsyncListener, or ServletRequestListener

The session context is shared among all servlet requests that occur within the same HTTPSession.

The session context is destroyed when the following occurs:

· When the HTTPSession times out due to inactivity, but only after all HttpSessionListener instances were called

· At the end of any request in which invalidate() was called, but only after all filters and ServletRequestListener instances were called

The application context lifecycle

The application context is the context object for the @ApplicationScoped built-in scope annotation.

The application context is active not only during all the same calls as that of the request context, but also when a disposer method or an @PreDestroy method of any bean with any normal scope apart from @ApplicationScoped is called.

It is shared between all servlet requests and method calls in which it is active within the same application.

The application context is destroyed when the application that it is associated with is shut down.

The conversation scope

The concept of a conversation will be familiar to those of us that have developed applications with Seam 2. In CDI, the conversation scope is very similar to the session scope, with the main difference being that its activation and deactivation is controlled by our application and not the container.

The conversation scope is also associated with a particular browser tab of a user, unlike the session, which is typically shared between tabs by browsers.

Each conversation is a way to represent a single unit of work that a user will perform to achieve a specific goal or task. As the conversation context holds the state associated with a unit of work by the user, there will be multiple conversations for a single user if they are working on multiple tasks at the same time.

The conversation context lifecycle

The conversation context is the context object for the @ConversationScoped built-in scope annotation.

The conversation context is active during any JSF faces or non-faces request.

Note

The conversation context implementation within Weld was developed to be used specifically with JSF, but it is possible to create an implementation for other web frameworks as well.

Every JSF request is associated with exactly one conversation, which is determined by Weld at the commencement of the JSF restore view phase and is not altered during the entirety of the request.

Note

In CDI 1.1, the tight coupling with JSF will be removed, enabling the conversation context to be used with other web frameworks.

The default behavior for a conversation, referred to as a transient conversation, is for it to be destroyed once a JSF request has completed. However, an application has the ability to promote it to a long-running conversation so that it remains active across multiple JSF requests.

Controlling the conversation lifecycle is achieved through a built-in bean that CDI provides just for this purpose:

@Inject

Conversation conversation;

A transient conversation can be made long-running by calling conversation.begin(), and a long-running conversation can be made transient again by calling conversation.end(). The following is an example of how we can control conversations:

@ConversationScoped

@Stateful

public class AccountRegistration {

private Account account;

@Inject

private Conversation conversation;

@PersistenceContext(type = EXTENDED)

EntityManager em;

@Produces

public Account getAccount() {

return account;

}

public Account createAccount() {

account = new Account();

conversation.begin();

return account;

}

public void saveAccount(Account account) {

em.persist(account);

conversation.end();
 }

}

The previous example will create a long-running conversation that has a unique identifier assigned to it by Weld, which is used for identifying a conversation and propagating it between requests.

Another option is to explicitly set a unique identifier of our own and pass it to begin() as follows:

conversation.begin("MyUniqueId");

Conversation propagation

Any long-running conversation that we have created is automatically propagated by Weld between JSF requests with any JSF faces request, resulting from the submission of a JSF form, or redirect. Any non-faces requests, such as a navigation link, will not have a long-running conversation propagated by Weld, but it is possible for us to force this behavior.

As we saw earlier, each long-running conversation has a unique identifier associated with it, which is propagated between JSF requests with the cid request parameter. We can use this parameter force propagation of the long-running conversation with the help of the following code:

<h:link outcome="/wizardFinish.xhtml" value="Finish">

<f:param name="cid" value="#{accountRegistration.conversationId}" />

</h:link>

Note

For the previous code snippet to work, we need to add getConversationId() onto the AccountRegistration bean and have it return conversation.getId().

When there are no long-running conversations propagated to a JSF request, then a new transient conversation is created for us. What if we have a long-running conversation but we want to start a new conversation and not have it automatically propagate? We can prevent propagation with the following code:

<h:link outcome="newPage.xhtml" value="Start">

<f:param name="nocid" value="true" />

</h:link>

Note

The nocid parameter is specific to Weld, but in CDI 1.1, the specification will include a parameter for this purpose: conversationPropagation=none.

Conversation timeout

There are occasions when a long-running conversation cannot be restored and associated with a new request. They are usually due to the following:

· HTTP servlet session invalidation

· Weld destroying it if it is not associated with a current JSF request, for the purposes of conserving resources

In the latter situation, we are able to provide a hint to Weld as to whether it's possible that a long-running conversation is likely to be used after a period of activity by setting a timeout as follows:

conversation.setTimeout(milliseconds);

Note

There is nothing in the CDI specification that mandates a container to adhere to any timeout set on a conversation; it is only meant as a hint as to whether it may be likely to be used again.

Pseudo scopes

Pseudo scopes result in beans that do not have a client proxy created by Weld, but instead a new instance is created each time and the client holds a direct reference to it. @Dependent is a scope annotation that is a pseudo scope type, and @Dependent is also the default scope for any beans that do not explicitly declare a scope.

Beans with a scope of @Dependent are never shared; it is a dependent object of whichever object it was injected into. This means that the @Dependent bean is created at the point when the object it belongs to is created, and is destroyed when the object it belongs to is destroyed.

Tip

Accessing a @Dependent bean by its EL name will cause a new instance of that bean to be created every time the expression is evaluated, which can be several times when using JSF. For this reason, it is not recommended to use @Dependent beans in EL expressions as it is likely that the desired behavior will not be achieved; instead, add the @Dependent bean to a normal scope bean and use a getter to retrieve the @Dependent bean from it.

Custom scopes

Creating a custom scope is usually the preserve of framework developers, but it can be beneficial for us to understand how they can be created as we may find the need to write one in the future.

To create a new pseudo scope we use the following code:

@Scope

@Retention(RUNTIME)

@Target( { METHOD, TYPE, FIELD } )

public @interface MyPseudoScope {}

But as it is unlikely that we will need to create a pseudo scope, the following is how to create a new normal scope:

@NormalScope

@Retention(RUNTIME)

@Target( { METHOD, TYPE, FIELD } )

public @interface MyNonPassivatingScope {}

As the default value for the passivating attribute on @NormalScope is false, we don't need to set it if we want to define a non-passivation capable scope.

Either of these scopes could then be used just as any other of the built-in scopes provided in CDI by annotating a bean with the scope annotation.

Creating the scope is the easy part; the difficult task is to implement a Context interface for the new scope. The Context interface provides operations for obtaining contextual instances with a particular scope of any contextual type by calling operations of Contextual.

Our application doesn't need to call the Context interface directly, but it will be called by Weld and maybe by CDI extensions. We will discuss how a CDI extension can be developed in Chapter 8, Writing a Portable Extension.

public interface Context {

public Class<? extends Annotation> getScope();

boolean isActive();

public <T> T get(Contextual<T> bean);

public <T> T get(Contextual<T> bean, CreationalContext<T> creationalContext);

}

Creating an implementation of Context for @MyNonPassivatingScope would mean the following:

· getScope() returns MyNonPassivatingScope .class.

· isActive() will return true only when the context object is active with respect to the current thread.

· get() obtains contextual instances of the contextual type represented by the instance of Contextual. If isActive() is false, we must throw a ContextNotActiveException exception. Both forms of get() will return an existing instance if found, but if one isn't found it will return the following:

· null if no CreationalContext is given or if Contextual.create() returns null

· A newly created instance of the contextual type if a CreationalContext is provided

For Weld to know about our implementation of Context, we need to register it with the container. We can do that within a CDI extension by observing the AfterBeanDiscovery event from Weld, as shown in the following code snippet:

public MyExtension implements Extension {

void afterBeanDiscovery(@Observes AfterBeanDiscovery event, BeanManager manager) {

event.addContext(new MyNonPassivatingScopeContext());

}

}

Note

The implementation of a Context object is a difficult task, which is why the example implementation in the code for this chapter has only implemented the methods in a way that enables the Context object to be registered with Weld.

Summary

We've now learned in greater detail about scopes and contexts, and how they relate to each other. The lifecycles for the request, session, and application scopes were explained with regards to how they relate to the lifecycle of a web container, with a detailed explanation of the conversation scope for JSF.

Normal and pseudo scopes were differentiated, and we talked about @Dependent, which is a built-in pseudo scope.

Lastly, we showed how framework developers can create their own scope and context for CDI, for their own or everyone's applications.