Sunday, December 3, 2017

How to obtain the contents of a static ThreadLocal by subclassing the Thread class.

To thoroughly unit test a Spring @Async method, it was necessary to check the contents of a static ThreadLocal being used by the method after the method had finished executing.

Because the contents of a static ThreadLocal variable, unlike a normal static variable,  are not shared between thread executions even though the variable has the same object id in all execution contexts, it was not possible to call the @Async method and then just check the static ThreadLocal in the test Thread after the execution of the method had finished.

To access the contents of a static ThreadLocal used by the @Async method, it was necessary to subclass the Thread class and register the subclass with the ThreadFactory being used by the Spring AsyncTaskExecutor.

When the AsyncTaskExecutor needed a Thread to execute the @Async method, it instantiated one of the subclassed Threads and passed it a reference to a Runnable, which was the Runnable of the @Async method.

Eventaully when the AsyncTaskExecutor started the subclassed Thread,  the subclassed Thread first called the @Async method by calling "run" on the Runnable it had been passed during construction and then accessed the static ThreadLocal afterwards. The contents of the ThreadLocal was then passed back to the main Thread through a normal static variable.

To see the full code of what is shown below, please go here.

ThreadFactory threadFactory = new ThreadFactory() {
    @Override    public Thread newThread(Runnable r) {
        return new Thread(r) {
            @Override            public void run() {
                r.run();
                MyLocalContext.passBack = MyLocalContext.threadLocal.get();
            }
        };
    }
};

ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setThreadFactory(threadFactory);

class MyLocalContext {
    public final static ThreadLocal<String> threadLocal = new ThreadLocal<>();
    public static String passBack;
}