Adam Bien's Weblog

Wednesday Nov 11, 2009

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] 


[my tweets]  Rss My book: Real World Java EE - Rethinking Best Practices

Kommentare:

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.

Gesendet von e.wernli am November 11, 2009 at 04: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

Gesendet von Detlef am November 11, 2009 at 05: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.

Gesendet von Gunnar am November 11, 2009 at 10:06 PM CET #

What's wrong with JACC?

Gesendet von Frank Cornelis am November 13, 2009 at 03:19 AM CET #

Senden Sie einen Kommentar:
  • HTML Syntax: Ausgeschaltet
Interviews/About
My Recent Book
Java One 2009
CommunityOne East N.Y.C
JavaONE 2008 Interview
Search
...the last 150 posts
...the last 10 comments
greenfire.dev.java.net
Links
my.netbeans.org
Visitors
License