可觀察性支援

Micrometer 定義了一個 Observation 概念,可以在應用程式中同時啟用 Metrics 和 Traces。Metrics 支援提供了一種建立計時器、量表或計數器的方法,用於收集有關應用程式執行時行為的統計資訊。 Metrics 可以幫助您追蹤錯誤率、使用模式、效能等等。 Traces 提供了整個系統的整體視圖,跨越應用程式邊界;您可以放大特定的使用者請求,並追蹤它們在應用程式中的完整完成過程。

如果配置了 ObservationRegistry,Spring 框架會檢測自身程式碼庫的各個部分以發布 observation。您可以進一步了解 在 Spring Boot 中配置可觀察性基礎架構

產生的 Observation 列表

Spring 框架檢測了各種功能以實現可觀察性。如本節 開頭所述,observations 可以根據配置產生計時器 Metrics 和/或 Traces。

表 1. Spring 框架產生的 Observation
Observation 名稱 描述

"http.client.requests"

HTTP 客户端交換所花費的時間

"http.server.requests"

框架層級 HTTP 伺服器交換的處理時間

"jms.message.publish"

訊息生產者將 JMS 訊息發送到目的地所花費的時間。

"jms.message.process"

先前由訊息消費者接收的 JMS 訊息的處理時間。

"tasks.scheduled.execution"

@Scheduled 任務執行的處理時間

Observations 正在使用 Micrometer 的官方命名慣例,但 Metrics 名稱將自動轉換為 監控系統後端偏好的格式 (Prometheus、Atlas、Graphite、InfluxDB…)。

Micrometer Observation 概念

如果您不熟悉 Micrometer Observation,以下是您應該了解的概念的快速摘要。

  • Observation 是應用程式中發生的某件事的實際記錄。它由 ObservationHandler 實作處理,以產生 metrics 或 traces。

  • 每個 observation 都有對應的 ObservationContext 實作;此類型保存用於提取其元資料的所有相關資訊。在 HTTP 伺服器 observation 的情況下,context 實作可以保存 HTTP 請求、HTTP 回應、處理期間拋出的任何例外等等。

  • 每個 Observation 都保存 KeyValues 元資料。在 HTTP 伺服器 observation 的情況下,這可以是 HTTP 請求方法、HTTP 回應狀態等等。此元資料由 ObservationConvention 實作貢獻,這些實作應宣告它們支援的 ObservationContext 類型。

  • 如果 KeyValue tuple 的可能值數量較少且有界限(HTTP 方法就是一個很好的例子),則 KeyValues 被稱為「低基數」。低基數值僅貢獻給 metrics。相反,「高基數」值是無界的(例如,HTTP 請求 URI),並且僅貢獻給 traces。

  • ObservationDocumentation 記錄特定網域中的所有 observations,列出預期的金鑰名稱及其含義。

配置 Observations

全域配置選項在 ObservationRegistry#observationConfig() 層級可用。每個檢測元件將提供兩個擴充點

  • 設定 ObservationRegistry;如果未設定,則不會記錄 observations,並且將為 no-ops

  • 提供自訂 ObservationConvention 以變更預設 observation 名稱和提取的 KeyValues

使用自訂 Observation 慣例

讓我們以 Spring MVC "http.server.requests" metrics 檢測與 ServerHttpObservationFilter 為例。此 observation 使用具有 ServerRequestObservationContextServerRequestObservationConvention;自訂慣例可以在 Servlet 篩選器上配置。如果您想自訂使用 observation 產生的元資料,您可以擴充 DefaultServerRequestObservationConvention 以滿足您的需求

import io.micrometer.common.KeyValue;
import io.micrometer.common.KeyValues;

import org.springframework.http.server.observation.DefaultServerRequestObservationConvention;
import org.springframework.http.server.observation.ServerRequestObservationContext;

public class ExtendedServerRequestObservationConvention extends DefaultServerRequestObservationConvention {

	@Override
	public KeyValues getLowCardinalityKeyValues(ServerRequestObservationContext context) {
		// here, we just want to have an additional KeyValue to the observation, keeping the default values
		return super.getLowCardinalityKeyValues(context).and(custom(context));
	}

	private KeyValue custom(ServerRequestObservationContext context) {
		return KeyValue.of("custom.method", context.getCarrier().getMethod());
	}

}

如果您想要完全控制,您可以為您感興趣的 observation 實作整個慣例契約

import java.util.Locale;

import io.micrometer.common.KeyValue;
import io.micrometer.common.KeyValues;

import org.springframework.http.server.observation.ServerHttpObservationDocumentation;
import org.springframework.http.server.observation.ServerRequestObservationContext;
import org.springframework.http.server.observation.ServerRequestObservationConvention;

public class CustomServerRequestObservationConvention implements ServerRequestObservationConvention {

	@Override
	public String getName() {
		// will be used as the metric name
		return "http.server.requests";
	}

	@Override
	public String getContextualName(ServerRequestObservationContext context) {
		// will be used for the trace name
		return "http " + context.getCarrier().getMethod().toLowerCase(Locale.ROOT);
	}

	@Override
	public KeyValues getLowCardinalityKeyValues(ServerRequestObservationContext context) {
		return KeyValues.of(method(context), status(context), exception(context));
	}


	@Override
	public KeyValues getHighCardinalityKeyValues(ServerRequestObservationContext context) {
		return KeyValues.of(httpUrl(context));
	}

	private KeyValue method(ServerRequestObservationContext context) {
		// You should reuse as much as possible the corresponding ObservationDocumentation for key names
		return KeyValue.of(ServerHttpObservationDocumentation.LowCardinalityKeyNames.METHOD, context.getCarrier().getMethod());
	}

	// status(), exception(), httpUrl()...

	private KeyValue status(ServerRequestObservationContext context) {
		return KeyValue.of(ServerHttpObservationDocumentation.LowCardinalityKeyNames.STATUS, String.valueOf(context.getResponse().getStatus()));
	}

	private KeyValue exception(ServerRequestObservationContext context) {
		String exception = (context.getError() != null ? context.getError().getClass().getSimpleName() : KeyValue.NONE_VALUE);
		return KeyValue.of(ServerHttpObservationDocumentation.LowCardinalityKeyNames.EXCEPTION, exception);
	}

	private KeyValue httpUrl(ServerRequestObservationContext context) {
		return KeyValue.of(ServerHttpObservationDocumentation.HighCardinalityKeyNames.HTTP_URL, context.getCarrier().getRequestURI());
	}

}

您也可以使用自訂 ObservationFilter 達成類似的目標 – 為 observation 新增或移除金鑰值。篩選器不會取代預設慣例,而是用作後處理元件。

import io.micrometer.common.KeyValue;
import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationFilter;

import org.springframework.http.server.observation.ServerRequestObservationContext;

public class ServerRequestObservationFilter implements ObservationFilter {

	@Override
	public Observation.Context map(Observation.Context context) {
		if (context instanceof ServerRequestObservationContext serverContext) {
			context.setName("custom.observation.name");
			context.addLowCardinalityKeyValue(KeyValue.of("project", "spring"));
			String customAttribute = (String) serverContext.getCarrier().getAttribute("customAttribute");
			context.addLowCardinalityKeyValue(KeyValue.of("custom.attribute", customAttribute));
		}
		return context;
	}
}

您可以在 ObservationRegistry 上配置 ObservationFilter 實例。

@Scheduled 任務檢測

每個 @Scheduled 任務的執行 建立一個 Observation。應用程式需要在 ScheduledTaskRegistrar 上配置 ObservationRegistry 以啟用 observation 的記錄。這可以透過宣告設定 observation registry 的 SchedulingConfigurer bean 來完成

import io.micrometer.observation.ObservationRegistry;

import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;

public class ObservationSchedulingConfigurer implements SchedulingConfigurer {

	private final ObservationRegistry observationRegistry;

	public ObservationSchedulingConfigurer(ObservationRegistry observationRegistry) {
		this.observationRegistry = observationRegistry;
	}

	@Override
	public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
		taskRegistrar.setObservationRegistry(this.observationRegistry);
	}

}

預設情況下,它使用 org.springframework.scheduling.support.DefaultScheduledTaskObservationConvention,並由 ScheduledTaskObservationContext 支援。您可以直接在 ObservationRegistry 上配置自訂實作。在排程方法執行期間,目前的 observation 會在 ThreadLocal context 或 Reactor context(如果排程方法傳回 MonoFlux 類型)中還原。

預設情況下,會建立以下 KeyValues

表 2. 低基數金鑰

名稱

描述

code.function (必要)

排程執行的 Java Method 名稱。

code.namespace (必要)

保存排程方法的 bean 實例的類別的正規名稱,或匿名類別的 "ANONYMOUS"

error (必要)

執行期間拋出的例外的類別名稱,如果沒有發生例外,則為 "none"

exception (已棄用)

重複 error 金鑰,並且未來可能會移除。

outcome (必要)

方法執行的結果。可以是 "SUCCESS""ERROR""UNKNOWN"(例如,如果操作在執行期間被取消)。

JMS 訊息傳輸檢測

如果類別路徑上有 io.micrometer:micrometer-jakarta9 依賴項,Spring 框架會使用 Micrometer 提供的 Jakarta JMS 檢測。io.micrometer.jakarta9.instrument.jms.JmsInstrumentation 檢測 jakarta.jms.Session 並記錄相關的 observations。

此檢測將建立 2 種類型的 observations

  • "jms.message.publish",當 JMS 訊息傳送到 broker 時,通常使用 JmsTemplate

  • "jms.message.process",當 JMS 訊息由應用程式處理時,通常使用 MessageListener@JmsListener 註解方法。

目前沒有 "jms.message.receive" observations 的檢測,因為測量等待接收訊息所花費的時間沒有太大價值。此類整合通常會檢測 MessageConsumer#receive 方法呼叫。但是一旦這些呼叫傳回,處理時間就不會被測量,並且追蹤作用域無法傳播到應用程式。

預設情況下,這兩種 observations 共用同一組可能的 KeyValues

表 3. 低基數金鑰

名稱

描述

error

訊息傳輸操作期間拋出的例外的類別名稱(或 "none")。

exception (已棄用)

重複 error 金鑰,並且未來可能會移除。

messaging.destination.temporary (必要)

目的地是否為 TemporaryQueueTemporaryTopic(值:"true""false")。

messaging.operation (必要)

正在執行的 JMS 操作的名稱(值:"publish""process")。

表 4. 高基數金鑰

名稱

描述

messaging.message.conversation_id

JMS 訊息的關聯 ID。

messaging.destination.name

目前訊息傳送到的目的地的名稱。

messaging.message.id

訊息系統用作訊息識別符的值。

JMS 訊息發布檢測

當 JMS 訊息傳送到 broker 時,會記錄 "jms.message.publish" observations。它們測量發送訊息所花費的時間,並使用外發 JMS 訊息標頭傳播追蹤資訊。

您需要在 JmsTemplate 上配置 ObservationRegistry 以啟用 observations

import io.micrometer.observation.ObservationRegistry;
import jakarta.jms.ConnectionFactory;

import org.springframework.jms.core.JmsMessagingTemplate;
import org.springframework.jms.core.JmsTemplate;

public class JmsTemplatePublish {

	private final JmsTemplate jmsTemplate;

	private final JmsMessagingTemplate jmsMessagingTemplate;

	public JmsTemplatePublish(ObservationRegistry observationRegistry, ConnectionFactory connectionFactory) {
		this.jmsTemplate = new JmsTemplate(connectionFactory);
		// configure the observation registry
		this.jmsTemplate.setObservationRegistry(observationRegistry);

		// For JmsMessagingTemplate, instantiate it with a JMS template that has a configured registry
		this.jmsMessagingTemplate = new JmsMessagingTemplate(this.jmsTemplate);
	}

	public void sendMessages() {
		this.jmsTemplate.convertAndSend("spring.observation.test", "test message");
	}

}

預設情況下,它使用 io.micrometer.jakarta9.instrument.jms.DefaultJmsPublishObservationConvention,並由 io.micrometer.jakarta9.instrument.jms.JmsPublishObservationContext 支援。

當回應訊息從 listener 方法傳回時,也會使用 @JmsListener 註解方法記錄類似的 observations。

JMS 訊息處理檢測

當 JMS 訊息由應用程式處理時,會記錄 "jms.message.process" observations。它們測量處理訊息所花費的時間,並使用傳入 JMS 訊息標頭傳播追蹤 context。

大多數應用程式將使用 @JmsListener 註解方法機制來處理傳入訊息。您需要確保在專用的 JmsListenerContainerFactory 上配置 ObservationRegistry

import io.micrometer.observation.ObservationRegistry;
import jakarta.jms.ConnectionFactory;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jms.annotation.EnableJms;
import org.springframework.jms.config.DefaultJmsListenerContainerFactory;

@Configuration
@EnableJms
public class JmsConfiguration {

	@Bean
	public DefaultJmsListenerContainerFactory jmsListenerContainerFactory(ConnectionFactory connectionFactory, ObservationRegistry observationRegistry) {
		DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
		factory.setConnectionFactory(connectionFactory);
		factory.setObservationRegistry(observationRegistry);
		return factory;
	}

}

需要預設容器 factory 才能啟用註解支援,但請注意,@JmsListener 註解可以參考特定用途的特定容器 factory bean。在所有情況下,只有在容器 factory 上配置了 observation registry 時,才會記錄 Observations。

當訊息由 MessageListener 處理時,也會使用 JmsTemplate 記錄類似的 observations。此類 listeners 設定在會話回呼中的 MessageConsumer 上(請參閱 JmsTemplate.execute(SessionCallback<T>))。

此 observation 預設使用 io.micrometer.jakarta9.instrument.jms.DefaultJmsProcessObservationConvention,並由 io.micrometer.jakarta9.instrument.jms.JmsProcessObservationContext 支援。

HTTP 伺服器檢測

對於 Servlet 和 Reactive 應用程式,使用名稱 "http.server.requests" 建立 HTTP 伺服器交換 observations。

Servlet 應用程式

應用程式需要在其應用程式中配置 org.springframework.web.filter.ServerHttpObservationFilter Servlet 篩選器。預設情況下,它使用 org.springframework.http.server.observation.DefaultServerRequestObservationConvention,並由 ServerRequestObservationContext 支援。

只有當 Exception 未被 Web 框架處理並冒泡到 Servlet 篩選器時,才會將 observation 記錄為錯誤。通常,Spring MVC 的 @ExceptionHandlerProblemDetail 支援 處理的所有例外都不會與 observation 一起記錄。您可以在請求處理期間的任何時間,自行在 ObservationContext 上設定錯誤欄位

import jakarta.servlet.http.HttpServletRequest;

import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.filter.ServerHttpObservationFilter;

@Controller
public class UserController {

	@ExceptionHandler(MissingUserException.class)
	ResponseEntity<Void> handleMissingUser(HttpServletRequest request, MissingUserException exception) {
		// We want to record this exception with the observation
		ServerHttpObservationFilter.findObservationContext(request)
				.ifPresent(context -> context.setError(exception));
		return ResponseEntity.notFound().build();
	}

	static class MissingUserException extends RuntimeException {
	}

}
由於檢測是在 Servlet 篩選器層級完成的,因此觀察範圍僅涵蓋在此篩選器之後排序的篩選器以及請求的處理。通常,Servlet 容器錯誤處理在較低的層級執行,並且不會有任何主動觀察或跨度。對於此用例,需要容器特定的實作,例如 Tomcat 的 org.apache.catalina.Valve;這超出了本專案的範圍。

預設情況下,會建立以下 KeyValues

表 5. 低基數鍵

名稱

描述

error (必要)

交換期間拋出的異常的類別名稱,如果沒有發生異常,則為 "none"

exception (已棄用)

重複 error 金鑰,並且未來可能會移除。

method (必填)

HTTP 請求方法的名稱,如果不是知名的方法,則為 "none"

outcome (必要)

HTTP 伺服器交換的結果。

status (必填)

HTTP 回應原始狀態碼,如果沒有建立回應,則為 "UNKNOWN"

uri (必填)

如果可用,則為符合處理程序的 URI 模式,對於 3xx 回應則回退到 REDIRECTION,對於 404 回應則回退到 NOT_FOUND,對於沒有路徑資訊的請求則回退到 root,對於所有其他請求則回退到 UNKNOWN

表 6. 高基數鍵

名稱

描述

http.url (必填)

HTTP 請求 URI。

反應式應用程式

應用程式需要使用 MeterRegistry 配置 WebHttpHandlerBuilder 以啟用伺服器檢測。這可以在 WebHttpHandlerBuilder 上完成,如下所示

import io.micrometer.observation.ObservationRegistry;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.server.reactive.HttpHandler;
import org.springframework.web.server.adapter.WebHttpHandlerBuilder;

@Configuration(proxyBeanMethods = false)
public class HttpHandlerConfiguration {

	private final ApplicationContext applicationContext;

	public HttpHandlerConfiguration(ApplicationContext applicationContext) {
		this.applicationContext = applicationContext;
	}

	@Bean
	public HttpHandler httpHandler(ObservationRegistry registry) {
		return WebHttpHandlerBuilder.applicationContext(this.applicationContext)
				.observationRegistry(registry)
				.build();
	}
}

預設情況下,它使用 org.springframework.http.server.reactive.observation.DefaultServerRequestObservationConvention,並由 ServerRequestObservationContext 支援。

只有當 Exception 未被應用程式控制器處理時,才會將觀察記錄為錯誤。通常,Spring WebFlux 的 @ExceptionHandlerProblemDetail 支援 處理的所有異常將不會與觀察一起記錄。您可以在請求處理期間的任何時間點,自行在 ObservationContext 上設定錯誤欄位

import org.springframework.http.ResponseEntity;
import org.springframework.http.server.reactive.observation.ServerRequestObservationContext;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.server.ServerWebExchange;

@Controller
public class UserController {

	@ExceptionHandler(MissingUserException.class)
	ResponseEntity<Void> handleMissingUser(ServerWebExchange exchange, MissingUserException exception) {
		// We want to record this exception with the observation
		ServerRequestObservationContext.findCurrent(exchange.getAttributes())
				.ifPresent(context -> context.setError(exception));
		return ResponseEntity.notFound().build();
	}

	static class MissingUserException extends RuntimeException {
	}

}

預設情況下,會建立以下 KeyValues

表 7. 低基數鍵

名稱

描述

error (必要)

交換期間拋出的異常的類別名稱,如果沒有發生異常,則為 "none"

exception (已棄用)

重複 error 金鑰,並且未來可能會移除。

method (必填)

HTTP 請求方法的名稱,如果不是知名的方法,則為 "none"

outcome (必要)

HTTP 伺服器交換的結果。

status (必填)

HTTP 回應原始狀態碼,如果沒有建立回應,則為 "UNKNOWN"

uri (必填)

如果可用,則為符合處理程序的 URI 模式,對於 3xx 回應則回退到 REDIRECTION,對於 404 回應則回退到 NOT_FOUND,對於沒有路徑資訊的請求則回退到 root,對於所有其他請求則回退到 UNKNOWN

表 8. 高基數鍵

名稱

描述

http.url (必填)

HTTP 請求 URI。

HTTP 客户端檢測

HTTP 客户端交換觀察使用名稱 "http.client.requests" 建立,適用於阻塞式和反應式客户端。與伺服器端對應項不同,檢測直接在客户端中實作,因此唯一需要的步驟是在客户端上配置 ObservationRegistry

RestTemplate

應用程式必須在 RestTemplate 實例上配置 ObservationRegistry 以啟用檢測;否則,觀察將是「無操作」。Spring Boot 將自動配置已設定觀察登錄檔的 RestTemplateBuilder bean。

檢測預設使用 org.springframework.http.client.observation.ClientRequestObservationConvention,並由 ClientRequestObservationContext 支援。

表 9. 低基數鍵

名稱

描述

method (必填)

HTTP 請求方法的名稱,如果不是知名的方法,則為 "none"

uri (必填)

用於 HTTP 請求的 URI 範本,如果未提供,則為 "none"。僅考慮 URI 的路徑部分。

client.name (必填)

從請求 URI 主機衍生的客户端名稱。

status (必填)

HTTP 回應原始狀態碼,如果發生 IOException,則為 "IO_ERROR",如果未收到回應,則為 "CLIENT_ERROR"

outcome (必要)

HTTP 客户端交換的結果。

error (必要)

交換期間拋出的異常的類別名稱,如果沒有發生異常,則為 "none"

exception (已棄用)

重複 error 金鑰,並且未來可能會移除。

表 10. 高基數鍵

名稱

描述

http.url (必填)

HTTP 請求 URI。

RestClient

應用程式必須在 RestClient.Builder 上配置 ObservationRegistry 以啟用檢測;否則,觀察將是「無操作」。

檢測預設使用 org.springframework.http.client.observation.ClientRequestObservationConvention,並由 ClientRequestObservationContext 支援。

表 11. 低基數鍵

名稱

描述

method (必填)

HTTP 請求方法的名稱,如果無法建立請求,則為 "none"

uri (必填)

用於 HTTP 請求的 URI 範本,如果未提供,則為 "none"。僅考慮 URI 的路徑部分。

client.name (必填)

從請求 URI 主機衍生的客户端名稱。

status (必填)

HTTP 回應原始狀態碼,如果發生 IOException,則為 "IO_ERROR",如果未收到回應,則為 "CLIENT_ERROR"

outcome (必要)

HTTP 客户端交換的結果。

error (必要)

交換期間拋出的異常的類別名稱,如果沒有發生異常,則為 "none"

exception (已棄用)

重複 error 金鑰,並且未來可能會移除。

表 12. 高基數鍵

名稱

描述

http.url (必填)

HTTP 請求 URI。

WebClient

應用程式必須在 WebClient builder 上配置 ObservationRegistry 以啟用檢測;否則,觀察將是「無操作」。Spring Boot 將自動配置已設定觀察登錄檔的 WebClient.Builder bean。

檢測預設使用 org.springframework.web.reactive.function.client.ClientRequestObservationConvention,並由 ClientRequestObservationContext 支援。

表 13. 低基數鍵

名稱

描述

method (必填)

HTTP 請求方法的名稱,如果不是知名的方法,則為 "none"

uri (必填)

用於 HTTP 請求的 URI 範本,如果未提供,則為 "none"。僅考慮 URI 的路徑部分。

client.name (必填)

從請求 URI 主機衍生的客户端名稱。

status (必填)

HTTP 回應原始狀態碼,如果發生 IOException,則為 "IO_ERROR",如果未收到回應,則為 "CLIENT_ERROR"

outcome (必要)

HTTP 客户端交換的結果。

error (必要)

交換期間拋出的異常的類別名稱,如果沒有發生異常,則為 "none"

exception (已棄用)

重複 error 金鑰,並且未來可能會移除。

表 14. 高基數鍵

名稱

描述

http.url (必填)

HTTP 請求 URI。

應用程式事件和 @EventListener

Spring Framework 沒有為 @EventListener 呼叫 貢獻觀察,因為它們沒有此類檢測的正確語意。預設情況下,事件發布和處理是同步完成的,並且在同一個執行緒上完成。這表示在該任務執行期間,ThreadLocals 和日誌記錄上下文將與事件發布者相同。

如果應用程式全域配置了自訂的 ApplicationEventMulticaster,其策略是在不同的執行緒上排程事件處理,則情況不再如此。所有 @EventListener 方法都將在不同的執行緒上處理,在主事件發布執行緒之外。在這些情況下,Micrometer Context Propagation 程式庫 可以幫助傳播這些值,並更好地關聯事件的處理。應用程式可以配置選定的 TaskExecutor 以使用 ContextPropagatingTaskDecorator,後者裝飾任務並傳播上下文。為了使此功能正常運作,io.micrometer:context-propagation 程式庫必須存在於類別路徑中

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.SimpleApplicationEventMulticaster;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.core.task.support.ContextPropagatingTaskDecorator;

@Configuration
public class ApplicationEventsConfiguration {

	@Bean(name = "applicationEventMulticaster")
	public SimpleApplicationEventMulticaster simpleApplicationEventMulticaster() {
		SimpleApplicationEventMulticaster eventMulticaster = new SimpleApplicationEventMulticaster();
		SimpleAsyncTaskExecutor taskExecutor = new SimpleAsyncTaskExecutor();
		// decorate task execution with a decorator that supports context propagation
		taskExecutor.setTaskDecorator(new ContextPropagatingTaskDecorator());
		eventMulticaster.setTaskExecutor(taskExecutor);
		return eventMulticaster;
	}

}

同樣地,如果針對每個 @EventListener 註解方法在本機進行非同步選擇,方法是將 @Async 新增至其中,則您可以選擇一個 TaskExecutor,該執行緒透過參照其限定詞來傳播上下文。考慮到以下使用專用任務裝飾器配置的 TaskExecutor bean 定義

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.core.task.TaskExecutor;
import org.springframework.core.task.support.ContextPropagatingTaskDecorator;

@Configuration
public class EventAsyncExecutionConfiguration {

	@Bean(name = "propagatingContextExecutor")
	public TaskExecutor propagatingContextExecutor() {
		SimpleAsyncTaskExecutor taskExecutor = new SimpleAsyncTaskExecutor();
		// decorate task execution with a decorator that supports context propagation
		taskExecutor.setTaskDecorator(new ContextPropagatingTaskDecorator());
		return taskExecutor;
	}

}

使用 @Async 和相關限定詞註解事件監聽器將實現類似的上下文傳播結果

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

@Component
public class EmailNotificationListener {

	private final Log logger = LogFactory.getLog(EmailNotificationListener.class);

	@EventListener(EmailReceivedEvent.class)
	@Async("propagatingContextExecutor")
	public void emailReceived(EmailReceivedEvent event) {
		// asynchronously process the received event
		// this logging statement will contain the expected MDC entries from the propagated context
		logger.info("email has been received");
	}

}