Search results
An alternative approach to ThreadLocal using Spring
1. OVERVIEW
In a blog post published sometime ago, Multitenant applications using Spring Boot JPA Hibernate and Postgres I included some code to set to and to retrieve from, the tenant identifier (a discriminator for selecting its associated datasource) using a ThreadLocal reference:
- A context holder class for holding the tenant data:
- A Spring MVC interceptor (which could have been also done using a servlet filter) to set and clear such tenant identifier:
- And somewhere in the application:
I normally try to avoid using ThreadLocal and would also advise to limit their usage. They can be handy, solve difficult problems but can also introduce memory leaks.
In this post I discuss how to use Spring’s ThreadLocalTargetSource to prevent dealing directly with the dangerous ThreadLocal while practicing dependency injection and proper mocking in unit tests.
2. REQUIREMENTS
- Java 7+.
- Maven 3.2+.
- Familiarity with Spring Framework.
3. CREATE THE DEMO APP
This command will create a Maven project in a folder named threadlocaltargetsource-demo
with the actuator and web-related Spring Boot dependencies.
Let’s take a look at the relevant classes included in this demo application:
TenantStore.java
:
This class serves as the tenant data holder.
AppConfig.java
:
This is where Spring’s beans are instantiated. tenantFilter()
and tenantFilterRegistration()
are straightforward, tenantFilter()
instantiates a servlet filter and tenantFilterRegistration()
implements a Spring’s mechanism that allows dependencies to be injected in TenantFilter.java, a regular servlet filter.
The other beans look interesting:
-
threadLocalTenantStore bean. ThreadLocalTargetSource is useful when you need an object, a TenantStore.java instance in this case, to be created for each incoming request. The target (a TenantStore.java object) will be instantiated only once in each thread and will get removed from each thread’s threadLocals map when ThreadLocalTargetSource’s
destroy()
is called, for instance when the application is shutdown. -
tenantStore bean is the target object stored in each thread. It is required to be prototype-scoped in AbstractPrototypeBasedTargetSource, parent class of ThreadLocalTargetSource.
-
proxiedThreadLocalTargetSource bean. According to the documentation and source code comments TargetSources must run in a BeanFactory since they need to call the
getBean()
method to create a new prototype instance.
DemoResource.java
:
This class implements a simple API in which a TenantStore.java instance is injected.
We will see later how it would appear there is only one instance of TenantStore.java class for different requests, each holding different tenant data.
TenantFilter.java
:
This servlet filter takes care of setting the tenant identifier to the TenantStore.java holder and clearing it up during the servlet filter chain’s way out.
We will see later how it would appear there is only one instance of TenantStore.java class for different requests, each holding different tenant data.
4. PACKAGING AND RUNNING THE APP
This application can be run from your preferred IDE as a regular Java application or from command line:
or
Let’s place a breakpoint in the TenantFilter.java class:
TenantFilter breakpoint
and send a couple of simultaneous requests to the /demo
endpoint:
and
Both requests should have been suspended in the Tenant filter breakpoint, lets take a look at partial stack traces and attributes values:
First request to /demo
endpoint
Notice that tenantStore's id
is 119
and that tenantStore's tenantId
is set to tenant_1
.
Second request to /demo
endpoint
As for the second request, tenantStore's id
is also 119
but the tenantStore's tenantId
is set to tenant_2
. Interesting eh. All these while both requests are still being processed. Also notice http-nio-8080-exec-1
’s stack trace which corresponds to processing request #1.
As the execution of the requests is resumed, the response to the first request looks like:
and the response to the second request:
Let’s now see how a unit test for the DemoResource.java class would look like:
DemoResourceTest.java
:
TenantStore dependency is being mocked using Mockito and injected in the API implementation. A case could be made to prevent using @InjectMocks but that goes out of the scope of this post.
Compare this:
to how I used it earlier this year:
which is error-prone since the ThreadLocal would have to be cleared in every test that follows this pattern to prevent a possible misuse if another unit test that uses the same ThreadLocal runs in the same thread.
And that’s all.
Propagating Tenant data with ThreadLocalTargetSource, TaskDecorator in Multi-tenant Spring Boot applications.
Thanks for reading and as always, feedback is very much appreciated. If you found this post helpful and would like to receive updates when content like this gets published, sign up to the newsletter.
5. SOURCE CODE
Accompanying source code for this blog post can be found at:
6. REFERENCES
- https://docs.spring.io/spring/docs/3.0.0.RELEASE/reference/htmlsingle/spring-framework-reference.html#classic-aop-ts-threadlocal
- https://github.com/spring-projects/spring-framework/blob/bc14c5ba83e1f211628456bbccce7b2531aac58c/spring-aop/src/main/java/org/springframework/aop/target/ThreadLocalTargetSource.java