If building a non-reactive Java web service such as a REST service that acts as an interface to other upstream services it is common
to adopt a model of one-thread-per-request with no state held at the web service. This model can be deployed to servlet containers
and application servers with ease.
For any onward requests made to upstream services it is often necessary to populate the request with information from the incoming
request, possibly to identify the user to the upstream service, or to store a request-id/correlation-id which can be used to trace
processing through multiple systems.
This information is not normally passed from method to method through the web service code but is instead held in temporary storage
scoped to the original request. Since the one-thread-per-request model is used, this temporary storage normally ends up being
ThreadLocal variables. When requests are constructed for the upstream service information can be read from these ThreadLocal
Using Multiple Threads
Depending on the work to be done it may be necessary to submit multiple requests to an upstream service. By sticking with a single
thread these requests will be sent sequentially which may result in unacceptable performance for the client. We can
use an ExecutorService to execute multiple upstream requests concurrently, but the problem is ensuring any thread specific data
is in place on the worker threads that will perform the requests.
By using a new ExecutorService to manage the concurrent upstream requests we can make use of a ContextCopyingThreadFactory to
handle copying application specific context from the original thread to any new threads.
Code listing: ContextCopyingThreadFactory.java
The ContextCopyingThreadFactory depends on implementations of ContextCopier to perform the actual reading and writing of thread specific
data. The ContextCopyingThreadFactory ensures that ContextCopier#copy will be called on the constructor thread and that
ContextCopier#apply will be called on any new threads created by the ThreadFactory.
Implemenations of ContextCopier must ensure any transformations are applied to the copied data as appropriate for the application. For
example, if the thread specific data is some sort of cache the implementation may choose to reuse the cache across all threads or create
copies depending on whether the cache is considered thread-safe.