可观测性支持

Micrometer定义了一个Observation概念,可以在应用程序中同时启用指标和跟踪。指标支持提供了一种创建计时器、仪表或计数器的方式,用于收集关于应用程序运行时行为的统计信息。指标可以帮助您跟踪错误率、使用模式、性能等。跟踪提供了对整个系统的整体视图,跨越应用程序边界;您可以放大到特定用户请求并跟踪它们在应用程序之间的完整完成过程。

Spring Framework会在其自身代码库的各个部分进行仪表化,以发布Observations,如果配置了ObservationRegistry。您可以了解更多关于在Spring Boot中配置可观测性基础设施的信息。

生成的Observations列表

Spring Framework为可观测性仪表化了各种功能。正如在本节的开头所概述的,根据配置,Observations可以生成计时器指标和/或跟踪。

表1. Spring Framework生成的Observations
Observation名称 描述

"http.client.requests"

HTTP客户端交换所花费的时间

"http.server.requests"

框架级别的HTTP服务器交换处理时间

"jms.message.publish"

将JMS消息发送到目的地所花费的时间

"jms.message.process"

之前由消息消费者接收的JMS消息的处理时间

"tasks.scheduled.execution"

@Scheduled任务执行的处理时间

Observations使用Micrometer的官方命名约定,但指标名称将自动转换为监控系统后端(Prometheus、Atlas、Graphite、InfluxDB等)首选的格式。了解更多

Micrometer Observation概念

如果您对Micrometer Observation不熟悉,这里是您应该了解的概念的快速摘要。

  • Observation是在应用程序中发生的实际记录。这由ObservationHandler实现处理,以生成指标或跟踪。

  • 每个Observation都有一个对应的ObservationContext实现;这种类型保存了提取其相关元数据的所有信息。在HTTP服务器观察的情况下,上下文实现可以保存HTTP请求、HTTP响应、处理过程中抛出的任何异常等。

  • 每个Observation都包含KeyValues元数据。在HTTP服务器观察的情况下,这可能是HTTP请求方法、HTTP响应状态等。这些元数据由ObservationConvention实现贡献,应声明它们支持的ObservationContext类型。

  • 如果KeyValue元组的可能值数量较低且有界(HTTP方法是一个很好的例子),则称为“低基数”值,仅贡献给指标。相反,“高基数”值是无界的(例如,HTTP请求URI),仅贡献给跟踪。

  • ObservationDocumentation记录了特定领域中的所有Observations,列出了预期的键名及其含义。

配置观测

全局配置选项可在 ObservationRegistry#observationConfig() 级别上使用。每个被仪器化的组件将提供两个扩展点:

  • 设置 ObservationRegistry;如果未设置,观测将不会被记录并将成为 no-ops

  • 提供自定义 ObservationConvention 来更改默认观测名称和提取的 KeyValues

使用自定义观测约定

让我们以 Spring MVC "http.server.requests" 指标仪器化为例,使用 ServerHttpObservationFilter。此观测使用 ServerRequestObservationConventionServerRequestObservationContext;可以在 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) {
		// 这里,我们只想向观测添加一个额外的 KeyValue,保留默认值
		return super.getLowCardinalityKeyValues(context).and(custom(context));
	}

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

}

如果您想要完全控制,可以为您感兴趣的观测实现整个约定契约:

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() {
		// 将用作指标名称
		return "http.server.requests";
	}

	@Override
	public String getContextualName(ServerRequestObservationContext context) {
		// 将用于跟踪名称
		return "http " + context.getCarrier().getMethod().toLowerCase();
	}

	@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) {
		// 您应尽可能重用相应的 key 名称的 ObservationDocumentation
		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 任务 都会创建一个观测。应用程序需要在 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消息仪表化

如果类路径上存在io.micrometer:micrometer-jakarta9依赖项,Spring Framework将使用Micrometer提供的Jakarta JMS仪表化。 io.micrometer.jakarta9.instrument.jms.JmsInstrumentation 会对jakarta.jms.Session进行仪表化,并记录相关的观察结果。

此仪表化将创建两种类型的观察结果:

  • 当使用JmsTemplate发送JMS消息到代理时,会创建"jms.message.publish"观察结果。

  • 当应用程序处理JMS消息时,通常使用MessageListener@JmsListener注解的方法,会创建"jms.message.process"观察结果。

目前没有为"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消息发送到代理时,会记录"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

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也会记录类似的观察结果。这些监听器设置在会话回调中的MessageConsumer上(参见JmsTemplate.execute(SessionCallback<T>))。

默认情况下,此观察结果使用io.micrometer.jakarta9.instrument.jms.DefaultJmsProcessObservationConvention,支持io.micrometer.jakarta9.instrument.jms.JmsProcessObservationContext

HTTP 服务器仪表盘

使用名称"http.server.requests"为Servlet和响应式应用程序创建HTTP服务器交换观察。

Servlet应用程序

应用程序需要在其应用程序中配置org.springframework.web.filter.ServerHttpObservationFilter Servlet过滤器。它默认使用org.springframework.http.server.observation.DefaultServerRequestObservationConvention,由ServerRequestObservationContext支持。

只有在异常未被Web框架处理并冒泡到Servlet过滤器时,才会将观察记录为错误。通常,Spring MVC的@ExceptionHandlerProblemDetail支持处理的所有异常都不会记录在观察中。您可以在请求处理过程中的任何时候自行设置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) {
		// 我们希望将此异常记录在观察中
		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) {
		// 我们希望记录此异常与观察
		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以启用仪器化;如果没有配置,观察将是"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构建器上配置一个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上下文传播库可以帮助传播这些值,并更好地关联事件的处理。应用程序可以配置所选的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();
		// 使用支持上下文传播的装饰器装饰任务执行
		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();
		// 使用支持上下文传播的装饰器装饰任务执行
		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) {
		// 异步处理接收到的事件
		// 此日志记录语句将包含从传播上下文中期望的MDC条目
		logger.info("已收到电子邮件");
	}

}