A new scope for Contexts and Dependency Injection – CDI

Dependency Injection is a great approach to decouple software components. Furthermore it is possible to inject the current context (state) of a software component. In Java EE 6 there are four scopes. With Java EE 7 a new scope was added. This post describes how to implement an additional scope that lives in its own lifecycle.

In Java EE 6, Contexts and Dependency Injection (CDI) is provided by JSR-299 [1]. It was extended by JSR-346 in Java EE7 [5]. As CDI is a specification, there are different implementations for CDI. The reference implementation for CDI is Weld [2], but there is also another implementation by Open WebBeans [3].
Furthermore there are other libraries that provide CDI. One of them is Google Guice. E.g. it is used within the Eclipse IDE. I simply want to mention it, as it is a great library and it really works perfectly. But with the introduction of Java EE 6, CDI is the standard for doing the same.

Even using Java SE it is no problem to use CDI, but you have to set up the CDI-Provider by yourself. When using a Java EE container, the container provides CDI to you. When using Guice you have to do this by yourself too. But Guice is meant to be used in Java SE.
In case there is unit testing, this is done outside of the container with Java SE. The CDI container must be set up manually. But there is a library called DeltaSpike [7]. DeltaSpike empowers you to set up different CDI-Containers in order to test your code. DeltaSpike is hosted by the Apache foundation. Members of WebBeans and Weld work together on DeltaSpike.

Introduction into Scopes

Per default in CDI there exist four scopes:

  • Request
  • Session
  • Application
  • Conversion

Number five was added by JTA 1.2 called TransactionScoped.

I don’t intend to explain the lifecycle of these four scopes as it is perfectly written down in the specification. As well there are so many tutorials you can find using the search engine of your choice. In order to get a fully detailed description of all the scopes, so please refer to the JSR-299 specification [1].
E.g. the request scope starts when a http request is sent to the server. While processing the request from a client, this scope is active. After the response is sent to the client, the context of this scope will be vanished. So in case there is an object that is requestscoped, within the scope always the same object is returned.

Why to build a new Scope in CDI

The four default scopes in CDI are quite useful, but this does not mean, this works for all situations. Because there may be use cases those scopes don’t work. E.g. you want to implement a cache for specific operations that automatically must be invalidated when leaving the context. This context might be a transaction or when a device or user enters or leaves a special state.

Building a new Scope

At first there must be distinguished the following two things:

  • Creation and destruction of all the context information
  • Storing the context information

The first depends on the technology (Cdi or Guice, …). The second on the kind of scope.

CDI in context of Java EE

The package javax.enterprise.context.spi provides the possiblity to extend CDI. This mechanism must be used in order to provide another CDI-Scope. At first there must be a class that extends javax.enterprise.context.spi.Context and that implements all the scoping. Furthermore javax.enterprise.inject.spi.Extension is required to be extended to add the scope at runtime to the cdi implementation.

A new Scope in context of Google Guice

Google Guice provides the implementation of scopes. There is no mechanism to store the information of these scopes. So storing and synchronizing this information must be implemented. In case there is a guarantee that there is no multithreaded access to this storage, it is allowed to be not thread safe.

Creating/Destroying contextual objects

When running in an Java EE container, creating a managed object is not that trivial as it seams. The container has to know the object. So when creating an object the container has to be notified. Furthermore there might be further injection of other objects to the contextual object. These other objects can be injected by the cdi provider only.

When implementing Context there are four methods.

public interface Context
{
  Class<? extends Annotation> getScope();
  <T> T get(Contextual<T> contextual, CreationalContext<T> creationalContext);
  <T> T get(Contextual<T> contextual);
  boolean isActive();
}

In case the container calls isActive it’s simple what to return. getScope returns the Annotation that is used to mark an object or provider as scoped. When the one parameter get is called then an already object is returned if existant, otherwise null>. The fourth method is the most important. In case there is no object, it must be created using CreationalContext and Contextual. Simply calling

Object o = contextual.create(creationalContext);

is required. Then the created object can be stored within the scope.

When stopping the scope each of the contextual objects must be destroyed. This can be done via calling destroy with the instance of Contextual, the instance of the object and the CreationalContext. In order to be able to destroy an object successfully, all this information must be stored within the scope. This is neccessary for the container to know what objects to use and which not to use any longer.

contextual.destroy(instance, creationalContext);

Storing Context Information

In Java EE each request is bound to a single thread. So all the processing within a request is put to ThreadLocal. So having the infomation stored to ThreadLocal works for this situation. Otherwise the context object may live in the scope that encloses the scope. That might be RequestScope for example.
When storing the information it is necessary to think about the situation whether it is possible to have a nested scope. That might happen when starting the scope within the scope. Two reasons can cause nested scopes:

  • It’s part of the concept
  • Misuse of the scope

In case it’s part of the concept, there must be a stack-like mechanism to push the current scope onto the stack when a new scope is created. When closing the scope, the state before the scope must be restored.
If it’s a misuse of the scope, no new scope must be started. I really don’t know how to handle this situation correctly. Throwing an exception might be one solution. Using the already open scope might be a second one. But closing this already open scope after usage might cause some severe problems as the scope may no be closed as it was already open at that point when opening a new one… Myself I prefer nested scopes, so this doesn’t bother me. :).

The following code demonstrates the simple case that there is only one scope at the same time.

public class ScopeState
{
    boolean isScopeActive;
    Map<Object, Object> objects = new HashMap<Object, Object>();
}

public class Scope implements Context
{
  private static final ThreadLocal<ScopeState> scopeState
    = new ThreadLocal<ScopeState>();

  public getObject(Object o)
  {
    return scopeState.objects.get(o);
  }

  public setObject(Object key, Object o)
  {
    scopeState.objects.put(key,o);
  }
}

Integrating the new Scope

As configuration in Java EE is done via file descriptors, the integration of a scope into an application is done this way too.

Building a cdi extension

Cdi provides an extension mechanism. This mechanism can be used to register a new scope. On startup cdi throws various events. You can register a component to catch these events. The code belows catches AfterBeanDiscovery so the new scope can be registered. In must simply extend the cdi spi interface Extension.

public class ScopeExtension implements Extension
{
  public void registerNewScope(@Observes AfterBeanDiscovery cdiEvent)
  {
    cdiEvent.addContext(new NewScope());
  }
}

Registering the extension

In order to integrate the scope an extension file must be used to give cdi an hint that there is a new scope. This is done the same way with other cdi mechanisms. To do this, simply place a file called META-INF/services/javax.enterprise.inject.spi.Extension into the jar that provides the new scope. Within this file only the full qualified name of the class that implements the cdi extentions must be provided. The content of the file looks like following:

com.robert_franz.cdi.scope.ScopeExtension

Using the scope

In order to use the scope simply annotate a class with the annotation the Scope is connected with. In this case NewScoped. Otherwise it may be used to annotate a producer filed or a producer method. The following three pieces of code show how the scope can be used.

@NewScoped
public class NewScopedBean
{
}
@Produces
@NewScoped
NewScopedBean bean
@Produces
@NewScoped
public NewScopedBean producesNewScopedBean()
{
  return new NewScopedBean();
}

Starting/Stopping the scope

And now ladies and gentlemen …

… the scope must be started. But how to do this? Well, in Java EE this could be done using an interceptor. As the context of this scope is bound to ThreadLocal this might be done via a static method. You must be able to get your current state and then set the new one to start the scope. After executing your code of choice stopping the scope even in case of exceptions is really really important. If this is procedure is not reliable you probably get a memory leak on your ThreadLocal. Your static method simply must run the start and stop method of your scope. Your interceptor furthermore should have a finally block that closes your scope in case it is open any more.

Last words

Implementing a scope for CDI is not complicated at all. But there is almost no information hoew to implemnt it. There is a lot of information how to use scopes and annotate classes and so on, but not how to do it by yourself. I hope this simplifies the implementation of writing own scopes for CDI.

Sources

[1] JSR-299 – CDI for Java EE
[2] Weld
[3] OpenWebBeans
[4] Google Guice
[5] JSR-346 – CDI for Java EE 7
[6] JSR-330 – Standard CDI-Annotations for Java
[7] Apache DeltaSpike

Leave a Reply

Your email address will not be published. Required fields are marked *

Time limit is exhausted. Please reload CAPTCHA.