可觀測性支援

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

如果配置了 ObservationRegistry,Spring Framework 會在其自身程式碼庫的各個部分進行檢測,以釋出可觀測性資料。您可以瞭解更多關於在 Spring Boot 中配置可觀測性基礎設施的資訊。

生成的觀測資料列表

Spring Framework 對各種功能進行了檢測,以實現可觀測性。如本節開頭所述,觀測資料可以根據配置生成定時器 Metrics 和/或 Traces。

表 1. Spring Framework 生成的觀測資料
觀測資料名稱 描述

"http.client.requests"

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

"http.server.requests"

Framework 級別 HTTP 伺服器交換的處理時間

"jms.message.publish"

訊息生產者向目標傳送 JMS 訊息所花費的時間。

"jms.message.process"

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

"tasks.scheduled.execution"

@Scheduled 任務執行的處理時間

觀測資料使用 Micrometer 的官方命名約定,但 Metrics 名稱將自動轉換為監控系統後端偏好的格式 (Prometheus, Atlas, Graphite, InfluxDB 等)。

Micrometer Observation 概念

如果您不熟悉 Micrometer Observation,這裡快速總結了一些您應該瞭解的概念。

  • Observation 是應用程式中發生事件的實際記錄。這由 ObservationHandler 實現處理,以生成指標或跟蹤。

  • 每個觀測資料都有一個相應的 ObservationContext 實現;這種型別包含所有相關資訊,用於提取其元資料。對於 HTTP 伺服器觀測資料,上下文實現可以包含 HTTP 請求、HTTP 響應、處理過程中丟擲的任何異常等。

  • 每個 Observation 都包含 KeyValues 元資料。對於 HTTP 伺服器觀測資料,這可以是 HTTP 請求方法、HTTP 響應狀態等。此元資料由 ObservationConvention 實現貢獻,它們應該宣告它們支援的 ObservationContext 型別。

  • 如果 KeyValue 元組可能值的數量很少且有限,則稱 KeyValues 為“低基數”(HTTP 方法是一個很好的例子)。低基數值僅用於指標。相反,“高基數”值是無界的(例如,HTTP 請求 URI),僅用於跟蹤。

  • ObservationDocumentation 記錄特定領域中的所有觀測資料,列出了預期的鍵名及其含義。

配置觀測資料

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

  • 設定 ObservationRegistry;如果未設定,將不會記錄觀測資料並作為無操作處理

  • 提供自定義 ObservationConvention 以更改預設觀測資料名稱和提取的 KeyValues

使用自定義觀測資料約定

以使用 ServerHttpObservationFilter 對 Spring MVC 的 "http.server.requests" 指標進行檢測為例。該觀測資料使用帶有 ServerRequestObservationContextServerRequestObservationConvention;可以在 Servlet 過濾器上配置自定義約定。如果您想自定義觀測資料產生的元資料,可以根據您的需求擴充套件 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());
	}

}

如果您想要完全控制,可以為您感興趣的觀測資料實現完整的約定契約

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 實現類似目標——新增或刪除觀測資料的鍵值。過濾器不會替代預設約定,而是作為後處理元件使用。

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 以啟用觀測資料的記錄。這可以透過宣告一個設定觀測資料註冊中心的 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 上配置自定義實現。在計劃方法的執行期間,當前觀測資料將在 ThreadLocal 上下文或 Reactor 上下文中恢復(如果計劃方法返回 MonoFlux 型別)。

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

表 2. 低基數鍵

名稱

描述

code.function (必需)

計劃執行的 Java Method 的名稱。

code.namespace (必需)

持有計劃方法的 bean 例項類的規範名稱,對於匿名類為 "ANONYMOUS"

error (必需)

執行期間丟擲的異常類名,如果沒有發生異常則為 "none"

exception (已棄用)

重複 error 鍵,將來可能會被移除。

outcome (必需)

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

JMS 訊息傳遞檢測

如果 classpath 中存在 io.micrometer:micrometer-jakarta9 依賴項,Spring Framework 將使用 Micrometer 提供的 Jakarta JMS 檢測。io.micrometer.jakarta9.instrument.jms.JmsInstrumentationjakarta.jms.Session 進行檢測並記錄相關的觀測資料。

此檢測將建立 2 種類型的觀測資料

  • 當 JMS 訊息傳送到 broker 時建立 "jms.message.publish" 觀測資料,通常使用 JmsTemplate

  • 當 JMS 訊息由應用程式處理時建立 "jms.message.process" 觀測資料,通常使用 MessageListener 或帶有 @JmsListener 註解的方法。

目前沒有針對 "jms.message.receive" 觀測資料的檢測,因為測量等待訊息接收所花費的時間價值不大。這種整合通常會檢測 MessageConsumer#receive 方法呼叫。但一旦這些呼叫返回,就不會測量處理時間,並且跟蹤範圍也無法傳播到應用程式。

預設情況下,這兩種觀測資料共享同一組可能的 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" 觀測資料。它們測量傳送訊息所花費的時間,並透過傳出的 JMS 訊息頭傳播跟蹤資訊。

您需要在 JmsTemplate 上配置 ObservationRegistry 以啟用觀測資料

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 提供支援。

當偵聽器方法返回響應訊息時,帶有 @JmsListener 註解的方法也會記錄類似的觀測資料。

JMS 訊息處理檢測

當 JMS 訊息由應用程式處理時,會記錄 "jms.message.process" 觀測資料。它們測量處理訊息所花費的時間,並透過傳入的 JMS 訊息頭傳播跟蹤上下文。

大多數應用程式將使用帶有 @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;
	}

}

需要一個預設的容器工廠來啟用註解支援,但請注意,@JmsListener 註解可以引用特定用途的特定容器工廠 bean。在所有情況下,只有在容器工廠上配置了觀測資料註冊中心時,才會記錄觀測資料。

當訊息由 MessageListener 處理時,使用 JmsTemplate 也會記錄類似的觀測資料。此類監聽器在會話回撥(參見 JmsTemplate.execute(SessionCallback<T>))中設定在 MessageConsumer 上。

預設情況下,此觀測資料使用 io.micrometer.jakarta9.instrument.jms.DefaultJmsProcessObservationConvention,並由 io.micrometer.jakarta9.instrument.jms.JmsProcessObservationContext 提供支援。

HTTP 伺服器檢測

HTTP 伺服器交換觀測資料以名稱 "http.server.requests" 建立,適用於 Servlet 和響應式應用程式。

Servlet 應用

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

只有當 Exception 未被 web 框架處理並冒泡到 Servlet 過濾器時,才會將觀測資料記錄為錯誤。通常,Spring MVC 的 @ExceptionHandlerProblemDetail 支援處理的所有異常都不會隨觀測資料一起記錄。您可以在請求處理的任何時候,自行在 ObservationContext 上設定 error 欄位

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 容器的錯誤處理在較低級別執行,不會有任何活動的觀測資料或 span。對於此用例,需要特定於容器的實現,例如 Tomcat 的 org.apache.catalina.Valve;這超出了本專案範圍。

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

表 5. 低基數鍵

名稱

描述

error (必需)

交換期間丟擲的異常類名,如果沒有發生異常則為 "none"

exception (已棄用)

重複 error 鍵,將來可能會被移除。

method (必需)

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

outcome (必需)

HTTP 伺服器交換的結果。

status (必需)

HTTP 響應原始狀態碼,如果未建立響應則為 "UNKNOWN"

uri (必需)

匹配 handler 的 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 未被應用程式 Controller 處理時,才會將觀測資料記錄為錯誤。通常,Spring WebFlux 的 @ExceptionHandlerProblemDetail 支援處理的所有異常都不會隨觀測資料一起記錄。您可以在請求處理的任何時候,自行在 ObservationContext 上設定 error 欄位

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 (必需)

匹配 handler 的 URI 模式(如果可用),對於 3xx 響應回退到 REDIRECTION,對於 404 響應回退到 NOT_FOUND,對於沒有路徑資訊的請求回退到 root,對於所有其他請求回退到 UNKNOWN

表 8. 高基數鍵

名稱

描述

http.url (必需)

HTTP 請求 URI。

HTTP 客戶端檢測

HTTP 客戶端交換觀測資料以名稱 "http.client.requests" 建立,適用於阻塞和響應式客戶端。此觀測資料測量整個 HTTP 請求/響應交換,從連線建立到請求體反序列化。與伺服器端不同,檢測直接在客戶端實現,因此唯一需要的步驟是在客戶端上配置 ObservationRegistry

RestTemplate

應用程式必須在 RestTemplate 例項上配置 ObservationRegistry 以啟用檢測;否則,觀測資料將作為 "no-ops"(無操作)處理。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 以啟用檢測;否則,觀測資料將作為 "no-ops"(無操作)處理。

預設情況下,檢測使用 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 以啟用檢測;否則,觀測資料將作為 "no-ops"(無操作)處理。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 呼叫貢獻 Observation,因為它們不具備此類檢測的正確語義。預設情況下,事件釋出和處理是同步的,並且在同一執行緒上完成。這意味著在該任務執行期間,ThreadLocals 和日誌上下文將與事件釋出者相同。

如果應用程式全域性配置了一個自定義的 ApplicationEventMulticaster,並且其策略是在不同的執行緒上排程事件處理,那麼這種情況就不再適用。所有 @EventListener 方法將在不同的執行緒上處理,而不是在主要的事件釋出執行緒上。在這種情況下,Micrometer Context Propagation library 可以幫助傳播這些值並更好地關聯事件的處理。應用程式可以配置所選的 TaskExecutor 來使用一個 ContextPropagatingTaskDecorator,它會裝飾任務並傳播上下文。為了使其工作,io.micrometer:context-propagation 庫必須存在於 classpath 中

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");
	}

}