Add ContextPropagatingTaskDecorator
Prior to this commit, `@Async` and `@EventListener` annotated methods would lose the the logging and observation contexts whenever their execution was scheduled on a different Thread. The Context Propagation library supports this use case and can propagate context values in ThreadLocals, Reactor Context and more. This commit introduces a new `TaskDecorator` implementation that leverages the Context Propagation library. When configured on a `TaskExecutor`, this allows to properly propagate context value through the execution of the task. This implementation is completely optional and requires the "io.micrometer:context-propagation" library on the classpath. Enabling this feature must be done consciously and sometimes selectively, as context propagation introduces some overhead. Closes gh-31130
This commit is contained in:
@@ -478,20 +478,21 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
Notice that `ApplicationListener` is generically parameterized with the type of your
|
||||
custom event (`BlockedListEvent` in the preceding example). This means that the
|
||||
`onApplicationEvent()` method can remain type-safe, avoiding any need for downcasting.
|
||||
You can register as many event listeners as you wish, but note that, by default, event
|
||||
listeners receive events synchronously. This means that the `publishEvent()` method
|
||||
blocks until all listeners have finished processing the event. One advantage of this
|
||||
synchronous and single-threaded approach is that, when a listener receives an event,
|
||||
it operates inside the transaction context of the publisher if a transaction context
|
||||
is available. If another strategy for event publication becomes necessary, e.g.
|
||||
asynchronous event processing by default, see the javadoc for Spring's
|
||||
{api-spring-framework}/context/event/ApplicationEventMulticaster.html[`ApplicationEventMulticaster`] interface
|
||||
and {api-spring-framework}/context/event/SimpleApplicationEventMulticaster.html[`SimpleApplicationEventMulticaster`]
|
||||
implementation for configuration options which can be applied to a custom
|
||||
"applicationEventMulticaster" bean definition.
|
||||
Notice that `ApplicationListener` is generically parameterized with the type of your custom event (`BlockedListEvent` in the preceding example).
|
||||
This means that the `onApplicationEvent()` method can remain type-safe, avoiding any need for downcasting.
|
||||
You can register as many event listeners as you wish, but note that, by default, event listeners receive events synchronously.
|
||||
This means that the `publishEvent()` method blocks until all listeners have finished processing the event.
|
||||
One advantage of this synchronous and single-threaded approach is that, when a listener receives an event,
|
||||
it operates inside the transaction context of the publisher if a transaction context is available.
|
||||
If another strategy for event publication becomes necessary, e.g. asynchronous event processing by default,
|
||||
see the javadoc for Spring's {api-spring-framework}/context/event/ApplicationEventMulticaster.html[`ApplicationEventMulticaster`] interface
|
||||
and {api-spring-framework}/context/event/SimpleApplicationEventMulticaster.html[`SimpleApplicationEventMulticaster`] implementation
|
||||
for configuration options which can be applied to a custom "applicationEventMulticaster" bean definition.
|
||||
In these cases, ThreadLocals and logging context are not propagated for the event processing.
|
||||
See xref:integration/observability.adoc#observability.application-events[the `@EventListener` Observability section]
|
||||
for more information on Observability concerns.
|
||||
|
||||
|
||||
|
||||
The following example shows the bean definitions used to register and configure each of
|
||||
the classes above:
|
||||
@@ -747,6 +748,9 @@ Be aware of the following limitations when using asynchronous events:
|
||||
value. If you need to publish another event as the result of the processing, inject an
|
||||
{api-spring-framework}/context/ApplicationEventPublisher.html[`ApplicationEventPublisher`]
|
||||
to publish the event manually.
|
||||
* ThreadLocals and logging context are not propagated by default for the event processing.
|
||||
See xref:integration/observability.adoc#observability.application-events[the `@EventListener` Observability section]
|
||||
for more information on Observability concerns.
|
||||
|
||||
|
||||
[[context-functionality-events-order]]
|
||||
|
||||
@@ -349,3 +349,28 @@ Instrumentation uses the `org.springframework.web.reactive.function.client.Clien
|
||||
|===
|
||||
|
||||
|
||||
[[observability.application-events]]
|
||||
== Application Events and `@EventListener`
|
||||
|
||||
Spring Framework does not contribute Observations for xref:core/beans/context-introduction.adoc#context-functionality-events-annotation[`@EventListener` calls],
|
||||
as they don't have the right semantics for such instrumentation.
|
||||
By default, event publication and processing is done synchronously and on the same Thread.
|
||||
This means that during the execution of that task, the ThreadLocals and logging context will be the same as the event publisher.
|
||||
|
||||
If the application configures globally a custom `ApplicationEventMulticaster` with a strategy that schedules event processing on different threads, this is no longer true.
|
||||
All `@EventListener` methods will be processed on a different thread, outstide of the main event publication thread.
|
||||
In these cases, the https://micrometer.io/docs/contextPropagation[Micrometer Context Propagation library] can help propagating such values and better correlate the processing of the events.
|
||||
The application can configure the chosen `TaskExecutor` to use a `ContextPropagatingTaskDecorator` that decorates tasks and propagates context.
|
||||
For this to work, the `io.micrometer:context-propagation` library must be present on the classpath:
|
||||
|
||||
include-code::./ApplicationEventsConfiguration[]
|
||||
|
||||
Similarly, if that asynchronous choice is made locally for each `@EventListener` annotated method, by adding an `@Async` method to it,
|
||||
you can choose a `TaskExecutor` that propagates context by referring to it by its qualifier.
|
||||
Given the following `TaskExecutor` bean definition, configured with the dedicated task decorator:
|
||||
|
||||
include-code::./EventAsyncExecutionConfiguration[]
|
||||
|
||||
Annotating event listeners with `@Async` and the relevant qualifier will achieve similar context propagation results:
|
||||
|
||||
include-code::./EmailNotificationListener[]
|
||||
|
||||
Reference in New Issue
Block a user