How To Pass Context Between Layers With ThreadLocal And EJB 3.(1)

TransactionSynchronizationRegistry is the way to go - you don't have to worry about the existence of multiple thread pools. However it only works inside a transaction. If you need to pass the context from the web layer (before a transaction is initiated), to the EJB container, where the transaction actually starts, you cannot use TransactionSynchronizationRegistry.

Additional data can be easily attached to the current request, using ThreadLocal. You can use again Interceptors / Servlet Filters for that purpose: 

import javax.interceptor.AroundInvoke;

import javax.interceptor.InvocationContext;

import static com.abien.patterns.kitchensink.contextholder.RegistryKey.*;

import static com.abien.patterns.kitchensink.contextholder.threadlocal.ThreadLocalContextHolder.*;

public class CurrentTimeMillisProvider {

    @AroundInvoke

    public Object injectMap(InvocationContext ic) throws Exception{

        put(KEY.name(), System.currentTimeMillis());

        return ic.proceed();

    }

 The ThreadLocalContextHolder is just a utility class with static methods, which can be statically imported:

import java.util.HashMap;

import java.util.Map;

public class ThreadLocalContextHolder {


    private static final ThreadLocal<Map<String,Object>> THREAD_WITH_CONTEXT = new ThreadLocal<Map<String,Object>>();


    private ThreadLocalContextHolder() {}


    public static void put(String key, Object payload) {

        if(THREAD_WITH_CONTEXT.get() == null){

            THREAD_WITH_CONTEXT.set(new HashMap<String, Object>());

        }

        THREAD_WITH_CONTEXT.get().put(key, payload);

    }


    public static Object get(String key) {

        return THREAD_WITH_CONTEXT.get().get(key);

    }


    public static void cleanupThread(){

        THREAD_WITH_CONTEXT.remove();

    }

The interface can be declared on a boundary / service facade:

@Stateless

@WebService

@Interceptors(CurrentTimeMillisProvider.class)

public class ServiceFacadeThreadLocalBean implements ServiceFacadeThreadLocal {

    @EJB

    private ServiceThreadLocal service;

    public void performSomeWork(){

        service.serviceInvocation();

    }

}

 The context will be passed along to the service and can be accessed from every method:

import javax.ejb.Stateless;

import static com.abien.patterns.kitchensink.contextholder.threadlocal.ThreadLocalContextHolder.*;

import static com.abien.patterns.kitchensink.contextholder.RegistryKey.*;

@Stateless

public class ServiceThreadLocalBean implements ServiceThreadLocal{


    public void serviceInvocation() {

        Long millis = (Long) get(KEY.name());

        System.out.println("Content is: " + millis);

    }

}

The ContextHolder pattern is only valuable, in case you have to pass the context in majority of all methods. You could of course extend DTOs with the additional context data, or enhance every method with an additional parameter. 

Because we are already in the lightweight Java EE 5 / 6 world - XML and other configuration plumbing are fully optional :-). 

A deployable, working example (ContextHolder) was tested with Glassfish v3 and NetBeans 6.8beta and pushed into http://kenai.com/projects/javaee-patterns/.  

[See Context Holder pattern, page 247 in "Real World Java EE Patterns Rethinking Best Practices" book for more in-depth discussion] 

Comments:

Beware however that contextual information will impact the testability and maintainability of your code.
- Contextual data will need to be set up in the test
- Contextual data are not present explicitly in the bean interface but participate in the overall contract of the bean.
Also, it will work only if everything is local so your are tied to a particular deployment scenario.
I used the same trick once but I've regret it later - should rather have refactored the code correctly.

Posted by e.wernli on November 11, 2009 at 05:57 PM CET #

Adam, thanks for taking my problem to your blog :-)

I did a lot of testing passing context data (contextDO) from a Servlet to an EJB (local call, within the same VM) using ThreadLocal.

I came to the conclusion that it doesn't work!

Of course it works in general, but as soon as your WebApp (servlets, filters etc. in a WAR) tries to pass context data using ThreadLocal to an EJB (in separate EAR) different classloaders come into play which breaks the whole concept (at least under WLS10).

Question: Does your approach work with different deployment units and thus different classloaders? I don't think so, but prove me wrong ;-)

Detlef

Posted by Detlef on November 11, 2009 at 06:44 PM CET #

Hi Adam, nice post. I just would like to point out that one really should take care to avoid memory leaks when using ThreadLocals, as threads are pooled in a JEE container.

So when returning threads to the pool, all ThreadLocal variables should be emptied. Another idea might be to use a SoftReference within the ThreadLocal which allows memory to be reclaimed when required.

Things can get even worse if ThreadLocal variables are initialized only conditionally and new requests happen to re-use ThreadLocal variables initialized by previous requests if no clean-up is in place.

Posted by Gunnar on November 11, 2009 at 11:06 PM CET #

What's wrong with JACC?

Posted by Frank Cornelis on November 13, 2009 at 04:19 AM CET #

1. Can't the InvocationContext#getContextData() be used for such simple use cases when the EJB Interceptor is setting some data which should be read by the invoked EJB method?

2. If I'll use ThreadLocal for something you've said - passing additional contextual data from web-layer - then can I be 100% sure that the thread that executed the Servlet (and which set the data in ThreadLocal) will be the same that'll execute my EJB method? (and go through my interceptor)

Posted by Piotr Nowicki on November 23, 2011 at 12:39 AM CET #

Post a Comment:
  • HTML Syntax: NOT allowed
...the last 150 posts
...the last 10 comments
License