20.Axon框架-消息

210 阅读25分钟

消息

1.介绍

在Axon中,组件之间的所有通信都是通过Message接口的对象表示的显式消息完成的

Axon中的核心概念之一是消息传递。组件之间的所有通信都通过消息对象完成。这为这些组件提供了必要的位置透明性,以便在需要时能够扩展和分布这些组件

2.类型

asdasd00000wio.png

3.组成

1767704124859.png

4.流转过程

1767769560022.png

asdasdasjoasjdoasdjdrawio1111111111.png

5.不可变特性

消息是不可变的。这意味着,若要添加新元素,实际上需要创建一个新的消息实例。为了能将两个Java对象实例视为代表同一个概念性消息,每个消息都有一个标识符。修改消息的元数据不会改变这个标识符

6.元数据

介绍

以键值对(String→Object)形式存在,描述消息发送的上下文,如跟踪信息、安全上下文等,典型用途为审计、日志记录,不应基于元数据做业务决策

创建

MetaData metaData = MetaData.with("myKey", 42)
                            .and("otherKey", "some value");

消息中的元数据

EventMessage eventMessage = GenericEventMessage
	// 消息体
    .asEventMessage("myPayload")
    // 替换所有元数据,只留下这个
    .withMetaData(Collections.singletonMap("myKey", 42))
    // 追加元数据
    .andMetaData(Collections.singletonMap("otherKey", "some value"));

不可变特性

与常规的Map不同,MetaData在Axon中的是不可变的。状态变更的方法将创建并返回一个新实例,而不是修改现有实例

由于MetaData是不可变的, 的所有变异操作Map都会抛出UnsupportedOperationException

7.消息关联

介绍

在消息传递系统中,将消息分组或关联是常见做法。在Axon Framework中,一个Command消息可能产生一个或多个Event消息,一个Query消息可能产生一个或多个QueryResponse消息。通常,关联通过特定的消息属性(即所谓的关联id,correlation identifier)来实现

CorrelationDataProvider

Axon Framework中的消息使用MetaData属性来传输消息的元信息。MetaData对象的类型为Map<String, Object>,会随消息一起传递。要填充在一个工作单元中生成的新消息的MetaData,可以使用所谓的CorrelationDataProvider。工作单元负责基于此CorrelationDataProvider填充新消息的MetaData

接口定义如下:

@FunctionalInterface
public interface CorrelationDataProvider {
    Map<String, ?> correlationDataFor(Message<?> message);
}

常见实现如下:

SimpleCorrelationDataProvider.png

MessageOriginProvider

介绍

MessageOriginProvider是默认的。它负责在消息之间传递两个值:correlationId和traceId

基本概念
  • correlationId:指向父消息的id
  • traceId:指向消息链的根消息id

当创建新消息时,如果父消息中不存在这两个字段,将使用新消息自身的标识符同时作为这两个值

案例理解

举个例子,如果你处理一个Command消息,而该Command消息又发布了一个Event消息,那么Event消息的MetaData将基于以下内容填充:

  • Command消息的id作为correlationId
  • Command消息的MetaData中若存在traceId,则使用该值;否则,使用Command消息的id作为traceId

SimpleCorrelationDataProvider

介绍

SimpleCorrelationDataProvider是无条件地将指定key的值从一个消息复制到另一个消息的元数据中。要实现这一点,必须调用SimpleCorrelationDataProvider的构造函数,并传入应被复制的key的列表

代码
public class Configuration {

    public CorrelationDataProvider customCorrelationDataProvider() {
        return new SimpleCorrelationDataProvider("myId", "myId2");
    }
}

MultiCorrelationDataProvider

介绍

MultiCorrelationDataProvider能够组合多个CorrelationDataProvider的效果。要实现这一点,必须调用MultiCorrelationDataProvider的构造函数,并传入一个提供器列表

代码
public class Configuration {

    public CorrelationDataProvider customCorrelationDataProviders() {
        return new MultiCorrelationDataProvider<CommandMessage<?>>(
            Arrays.asList(
                new SimpleCorrelationDataProvider("someKey"),
                new MessageOriginProvider()
            )
        );
    }
}

自定义

介绍

如果预定义的提供器不能满足你的需求,你始终可以实现自己的CorrelationDataProvider。该类必须实现CorrelationDataProvider接口

使用步骤
  1. 定义一个类实现CorrelationDataProvider接口
  2. 在配置中注册
实现
public class AuthCorrelationDataProvider implements CorrelationDataProvider {

    private final Function<String, String> usernameProvider;

    public AuthCorrelationDataProvider(Function<String, String> userProvider) {
        this.usernameProvider = userProvider;
    }

    @Override
    public Map<String, ?> correlationDataFor(Message<?> message) {
        Map<String, Object> correlationData = new HashMap<>();
        if (message instanceof CommandMessage<?>) {
            if (message.getMetaData().containsKey("authorization")) {
                String token = (String) message.getMetaData().get("authorization");
                correlationData.put("username", usernameProvider.apply(token));
            }
        }
        return correlationData;
    }
}
注册
原生API
public class Configuration {

    public void configuring() {
        Configurer configurer = DefaultConfigurer
            .defaultConfiguration()
            .configureCorrelationDataProviders(config -> Arrays.asList(
               new SimpleCorrelationDataProvider("someKey"),
               new MessageOriginProvider()
            ));
    }
}
SpringBoot
@Configuration
public class CorrelationDataProviderConfiguration {

    // 配置单个 CorrelationDataProvider 会自动覆盖默认的 MessageOriginProvider
    @Bean
    public CorrelationDataProvider someKeyCorrelationProvider() {
        return new SimpleCorrelationDataProvider("someKey");
    }

    @Bean
    public CorrelationDataProvider messageOriginProvider() {
        return new MessageOriginProvider();
    }
}

8.消息拦截

类型

  1. 命令拦截器:拦截命令
  2. 事件拦截器:拦截事件
  3. 查询拦截器:拦截查询

同时每种消息的拦截还有两种形式:

  1. 分发拦截器:前者在消息分发前调用,可修改元数据或阻断消息
  2. 处理器拦截器:执行前后调用,可控制处理流程或附加上下文

命令拦截器

分发拦截器
介绍

当命令在CommandBus上分发时,命令分发拦截器会被触发调用。其核心能力包括:

  1. 修改命令消息:例如为CommandMessage添加Metadata(元数据)
  2. 阻断命令分发:通过抛出异常阻止无效命令继续传递
注意

始终在命令分发线程(即发送命令的线程)中执行

使用步骤
  1. 实现MessageDispatchInterceptor接口
  2. 注册实现类
案例

以下示例创建一个MessageDispatchInterceptor,用于记录所有在CommandBus上分发的命令:

public class MyCommandDispatchInterceptor implements MessageDispatchInterceptor<CommandMessage<?>> {

    private static final Logger LOGGER = LoggerFactory.getLogger(MyCommandDispatchInterceptor.class);

    @Override
    public BiFunction<Integer, CommandMessage<?>, CommandMessage<?>> handle(List<? extends CommandMessage<?>> messages) {
        return (index, command) -> {
            LOGGER.info("Dispatching a command {}.", command);
            return command; // 返回原命令,不做修改
        };
    }
}

通过CommandBus的registerDispatchInterceptor方法注册拦截器,示例如下:

public class CommandBusConfiguration {

    public CommandBus configureCommandBus() {
        // 构建 SimpleCommandBus 实例
        CommandBus commandBus = SimpleCommandBus.builder().build();
        // 注册自定义分发拦截器
        commandBus.registerDispatchInterceptor(new MyCommandDispatchInterceptor());
        return commandBus;
    }
}
校验

若命令缺少必要信息或格式不正确,后续处理将无意义。因此,应尽早阻断无效命令(最好在事务启动前),这种校验逻辑称为结构验证

Axon Framework支持JSR 303 Bean Validation规范(即参数校验规范),实现步骤如下:

  1. 添加依赖:在类路径(classpath)中引入JSR 303实现(如Hibernate-Validator)
  2. 标注命令类:在Command类的字段上使用JSR 303注解(如 @NotEmpty、@Pattern、@Min)定义校验规则
  3. 注册校验拦截器:在CommandBus上配置BeanValidationInterceptor该拦截器会自动发现并使用类路径中的校验器(Validator)实现,默认规则已满足大部分场景,也可根据需求自定义
顺序

为减少无效命令对资源的消耗,BeanValidationInterceptor通常应放在拦截器链的最前端。特殊场景下(如需要先记录日志或审计),可将LoggingInterceptor或AuditingInterceptor置于最前,校验拦截器紧随其后

此外,BeanValidationInterceptor还实现了MessageHandlerInterceptor接口,因此也可作为命令处理器拦截器配置使用

处理拦截器
介绍

命令处理器拦截器可在命令处理前、处理后执行逻辑,甚至能完全阻断命令处理(如出于安全原因拒绝无权操作)

使用步骤
  1. 拦截器实现MessageHandlerInterceptor接口
  2. 注册拦截器到CommandBus
典型用途

常用于管理CommandHandler的事务通过注册TransactionManagingInterceptor实现,该拦截器需配置TransactionManager,用于在命令处理时启动事务,处理成功后提交,失败时回滚

案例

以下示例实现一个拦截器,仅允许Metadata中userId字段值为axonUser的命令被处理。若userId不存在或值不匹配,则阻断命令处理:

public class MyCommandHandlerInterceptor implements MessageHandlerInterceptor<CommandMessage<?>> {

    @Override
    public Object handle(UnitOfWork<? extends CommandMessage<?>> unitOfWork, InterceptorChain interceptorChain) throws Exception {
        // 从 UnitOfWork 中获取当前处理的 CommandMessage
        CommandMessage<?> command = unitOfWork.getMessage();
        // 从 Metadata 中获取 userId,不存在则抛出异常
        String userId = Optional.ofNullable(command.getMetaData().get("userId"))
                                .map(uId -> (String) uId) // 类型转换
                                .orElseThrow(IllegalCommandException::new); // 无 userId 时抛异常
        
        // 校验 userId,符合条件则继续执行拦截器链
        if ("axonUser".equals(userId)) {
            return interceptorChain.proceed();
        }
        // 不符合条件则返回 null,阻断命令处理
        return null;
    }
}

通过CommandBus的registerHandlerInterceptor方法注册,示例如下:

public class CommandBusConfiguration {

    public CommandBus configureCommandBus() {
        CommandBus commandBus = SimpleCommandBus.builder().build();
        // 注册自定义处理器拦截器
        commandBus.registerHandlerInterceptor(new MyCommandHandlerInterceptor());
        return commandBus;
    }

}

事件拦截器

分发拦截器
介绍

当事件通过EventBus发布时,注册在EventBus上的事件分发拦截器会被触发调用

注意:始终在事件发布线程中执行,不会引入跨线程开销

使用

以下示例创建MessageDispatchInterceptor<EventMessage<?>>实现类,用于记录所有通过EventBus发布的事件:

public class EventLoggingDispatchInterceptor
                implements MessageDispatchInterceptor<EventMessage<?>> {

    private static final Logger logger =
                LoggerFactory.getLogger(EventLoggingDispatchInterceptor.class);

    @Override
    public BiFunction<Integer, EventMessage<?>, EventMessage<?>> handle(
                List<? extends EventMessage<?>> messages) {
        // 对每个待发布的 EventMessage 执行日志记录
        return (index, event) -> {
            logger.info("Publishing event: [{}].", event);
            return event; // 返回原事件,不做修改(也可添加 Metadata 后返回新实例)
        };
    }
}

EventStore是EventBus的特定实现(包含事件持久化能力),因此拦截器注册逻辑同样适用于EventStore。示例如下:

public class EventBusConfiguration {

    public EventBus configureEventBus(EventStorageEngine eventStorageEngine) {
        // 注:EventStore 是 EventBus 的更具体实现(具备事件存储能力)
        EventBus eventBus = EmbeddedEventStore.builder()
                                              .storageEngine(eventStorageEngine) // 配置事件存储引擎
                                              .build();
        // 向 EventBus 注册事件分发拦截器
        eventBus.registerDispatchInterceptor(new EventLoggingDispatchInterceptor());
        return eventBus;
    }
}
处理器拦截器
介绍

事件处理器拦截器作用于事件处理阶段,可在事件处理前、处理后执行逻辑,甚至完全阻断事件处理(如拒绝未授权的事件消费),与命令处理器拦截器的设计思路一致

拦截器必须实现MessageHandlerInterceptor<EventMessage<?>>接口

参数
  1. UnitOfWork:当前工作单元,可通过unitOfWork.getMessage()获取待处理的EventMessage,并能在事件处理的不同阶段(如onPrepareCommit、onCleanup)注册逻辑
  2. InterceptorChain:拦截器链,调用interceptorChain.proceed()可继续执行后续拦截器或最终的EventHandler
使用

以下示例实现一个拦截器,仅允许Metadata中userId字段值为axonUser的事件被处理。若userId不存在或值不匹配,则阻断事件处理(抛出异常或不继续执行拦截器链):

public class MyEventHandlerInterceptor
        implements MessageHandlerInterceptor<EventMessage<?>> {

    @Override
    public Object handle(UnitOfWork<? extends EventMessage<?>> unitOfWork,
                         InterceptorChain interceptorChain) throws Exception {
        // 从 UnitOfWork 中获取当前待处理的 EventMessage
        EventMessage<?> event = unitOfWork.getMessage();
        // 从 Metadata 中提取 userId,不存在则抛出异常
        String userId = Optional.ofNullable(event.getMetaData().get("userId"))
                                .map(uId -> (String) uId) // 类型转换
                                .orElseThrow(IllegalEventException::new); // 无 userId 时阻断处理
        
        // 校验 userId,符合条件则继续执行后续逻辑
        if ("axonUser".equals(userId)) {
            return interceptorChain.proceed();
        }
        // 不符合条件则返回 null,阻断事件处理
        return null;
    }
}

事件处理由EventProcessor(如TrackingEventProcessor、SubscribingEventProcessor)负责,因此事件处理器拦截器需直接注册到EventProcessor上,而非EventBus。示例如下:

public class EventProcessorConfiguration {

    public void configureEventProcessing(Configurer configurer) {
        // 1. 注册一个名为 "my-tracking-processor" 的 TrackingEventProcessor
        // 2. 为该处理器注册自定义事件处理器拦截器
        configurer.eventProcessing()
                  .registerTrackingEventProcessor("my-tracking-processor")
                  .registerHandlerInterceptor(
                      "my-tracking-processor", // 目标 EventProcessor 名称
                      configuration -> new MyEventHandlerInterceptor() // 拦截器实例(支持基于配置动态创建)
                  );
    }
}

查询拦截器

介绍

使用QueryBus(查询总线)的优势之一是能够基于所有入站查询执行统一操作,例如日志记录、身份认证等这类与查询类型无关的横切逻辑,可通过查询拦截器实现

分发拦截器
介绍

当查询在QueryBus上分发,或查询更新(subscription update)通过QueryUpdateEmitter分发时,查询分发拦截器会被触发调用

注意:执行线程:始终在消息分发线程(即发送查询 / 分发更新的线程)中执行

核心功能
  1. 修改查询消息:可向QueryMessage或查询更新消息中添加Metadata(如追踪ID、请求来源)
  2. 阻断查询执行:通过抛出异常阻止无效查询继续传递到处理器
校验

若查询缺少必要信息或格式不正确,后续处理将无意义。因此,应尽早阻断无效查询,这种校验逻辑称为 “结构验证”,实现方式与命令的结构验证完全一致

顺序

与命令的分发拦截器顺序说明一致

处理器拦截器
介绍

查询处理器拦截器可在查询处理前、处理后执行逻辑,甚至能完全阻断查询处理(如出于安全原因拒绝未授权的查询操作)

使用

拦截器必须实现MessageHandlerInterceptor接口

9.特殊消息拦截方式

@CommandHandlerInterceptor

介绍

Axon支持在Aggregate或Entity内部,通过@CommandHandlerInterceptor注解将方法标记为命令处理器拦截器。这种方式与常规MessageHandlerInterceptor的核心区别是:可基于当前Aggregate的状态做拦截决策(常规拦截器无法直接访问Aggregate状态)

核心特性
  1. 作用范围:注解可标注在Aggregate内部的任意Entity(包括AggregateRoot和子实体)上
  2. 跨层级拦截:即使@CommandHandler方法定义在子实体中,也可在AggregateRoot层级定义拦截器拦截该命令
  3. 阻断命令:通过抛出异常(如IllegalCommandException)可阻止命令执行
  4. 拦截器链控制:方法参数可声明InterceptorChain,通过调用interceptorChain.proceed()控制是否继续执行命令
  5. 命令匹配:通过注解的commandNamePattern属性指定正则表达式,可拦截所有匹配名称的命令(如commandNamePattern = "Redeem.*Command" 拦截所有以Redeem开头的命令)
  6. 发布事件:可在注解拦截器方法中通过apply()方法发布Event(与Aggregate中@CommandHandler方法的事件发布逻辑一致)
案例

以下示例中,GiftCard(AggregateRoot)的@CommandHandlerInterceptor方法会校验命令的state字段是否与自身state一致,仅一致时才允许命令执行:

public class GiftCard {
    // .. 其他字段与逻辑 ..
    private String state; // Aggregate 自身的状态字段
    // .. 其他字段与逻辑 ..

    @CommandHandlerInterceptor
    public void intercept(RedeemCardCommand command, InterceptorChain interceptorChain) {
        // 校验命令状态与 Aggregate 状态是否一致
        if (this.state.equals(command.getState())) {
            interceptorChain.proceed(); // 一致则继续执行命令
        }
        // 不一致则不调用 proceed(),阻断命令处理
    }
}

注解式消息处理器拦截器

介绍

除了在处理消息的组件(@CommandHandler,@EventHandler,@QueryHandler)上定义全局的MessageHandlerInterceptor实例外,还可以为包含处理器的特定组件定义处理器拦截器

实现方式是:在处理消息的方法上添加@MessageHandlerInterceptor注解,这种方式能更精细地控制哪些消息处理组件需要响应拦截逻辑以及如何响应

核心特性
  1. 拦截器链控制:MessageHandlerInterceptor实例通过InterceptorChain决定何时执行链中其他拦截器,若方法中不包含该参数,框架会在拦截方法执行完毕后自动调用
  2. 指定消息类型:可定义拦截器需处理的Message类型,默认情况下拦截器会响应所有Message实现类。若需仅拦截特定类型(如仅拦截EventMessage),可通过注解的messageType参数指定目标类型
  3. 指定负载类型:为进一步精细化控制,可通过注解指定待处理Message中负载的类型,仅当消息的负载类型与指定类型匹配时,拦截器才会被触发
使用
案例1

拦截所有类型的Message,无InterceptorChain参数(框架自动执行后续拦截器):

public class CardSummaryProjection {
    /*
     * 该类中包含其他 @EventHandler 和 @QueryHandler 注解的方法
     */
    @MessageHandlerInterceptor
    public void intercept(Message<?> message) {
        // 此处添加基于 Message 的拦截逻辑(如日志记录、元数据检查)
    }
}
案例2

仅拦截EventMessage类型的消息,针对性处理事件相关逻辑:

public class CardSummaryProjection {
    /*
     * 该类中包含其他 @EventHandler 和 @QueryHandler 注解的方法
     */
    @MessageHandlerInterceptor(messageType = EventMessage.class)
    public void intercept(EventMessage<?> eventMessage) {
        // 此处添加基于 EventMessage 的拦截逻辑(如事件时间戳校验、聚合根 ID 提取)
    }
}
案例3

仅拦截负载类型为CardRedeemedEvent的EventMessage,实现最精细的拦截范围控制:

public class CardSummaryProjection {
    /*
     * 该类中包含其他 @EventHandler 和 @QueryHandler 注解的方法
     */
    @MessageHandlerInterceptor(
        messageType = EventMessage.class,  // 指定消息类型为 EventMessage
        payloadType = CardRedeemedEvent.class  // 指定消息负载类型为 CardRedeemedEvent
    )
    public void intercept(CardRedeemedEvent event) {
        // 此处直接基于 CardRedeemedEvent(负载)添加拦截逻辑(如 redemptionAmount 合法性校验)
    }
}
案例4

手动控制拦截器链执行时机,可在interceptorChain.proceed()前后添加自定义逻辑:

public class CardSummaryProjection {
    /*
     * 该类中包含其他 @EventHandler 和 @QueryHandler 注解的方法
     */
    @MessageHandlerInterceptor(messageType = QueryMessage.class)
    public void intercept(QueryMessage<?, ?> queryMessage,
                          InterceptorChain interceptorChain) throws Exception {
        // 1. 处理“执行拦截器链前”的逻辑(如查询权限校验)
        validateQueryPermission(queryMessage);
        
        // 2. 执行拦截器链(继续后续拦截器或最终的 QueryHandler)
        interceptorChain.proceed();
        
        // 3. 处理“执行拦截器链后”的逻辑(如查询结果缓存、日志记录)
        cacheQueryResult(queryMessage, ...);
    }
}
案例5

在聚合(Aggregate)中,@MessageHandlerInterceptor会遵循聚合层级结构执行,具体顺序为:

  1. 先执行聚合根(Aggregate Root)上的拦截器
  2. 再执行聚合成员(Aggregate Member,如子实体)上的拦截器
// 聚合根(Aggregate Root)
public class GiftCard {

    @AggregateIdentifier
    private String id;

    // 聚合成员(子实体列表)
    @AggregateMember
    private List<GiftCardTransaction> transactions = new ArrayList<>();

    @MessageHandlerInterceptor
    public void intercept(Message<?> message) {
        // 该拦截器会【先执行】!
    }

    // 省略构造函数、命令处理器、事件溯源处理器(Event Sourcing Handler)
}

// 聚合成员(子实体)
public class GiftCardTransaction {

    @EntityId
    private String transactionId;

    @MessageHandlerInterceptor
    public void intercept(Message<?> message) {
        // 该拦截器会【后执行】!
    }

    // 省略构造函数、命令处理器、事件溯源处理器及 equals/hashCode 方法
}
@EventSourcingHandler的拦截

如前文所述,@MessageHandlerInterceptor在聚合层级中会按 “聚合根 → 聚合成员” 的顺序执行,但该规则不适用于事件溯源处理器@EventSourcingHandler

  1. 当为聚合成员(如GiftCardTransaction)执行事件溯源时,仅会调用该成员自身的@MessageHandlerInterceptor,忽略聚合根(如GiftCard)上的拦截器
  2. 这是Axon Framework 4架构设计导致的差异,暂时无法调整。因此,不建议在聚合事件溯源过程中,依赖@MessageHandlerInterceptor的层级调用逻辑,避免出现预期外的拦截行为
参数

除了 Message、Payload和InterceptorChain外,@MessageHandlerInterceptor注解的方法还支持解析其他参数。具体支持哪些参数,取决于拦截器处理的Message类型(如CommandMessage、EventMessage、QueryMessage)

如需了解针对特定Message类型可解析的参数详情,可参考Axon官方文档的参数部分

@ExceptionHandler

介绍

@MessageHandlerInterceptor还支持一种更具体的拦截方法,即标注了@ExceptionHandler的方法

框架仅在消息处理返回异常结果时,才会调用@ExceptionHandler注解的方法。例如,通过此类异常处理器,你可以将数据库/服务抛出的异常转换为更具领域针对性的异常,或者捕获聚合特定的异常并将其转换为通用错误代码

对Axon而言,异常处理器与其他消息处理方法并无区别。因此,你可以像命令、事件和查询处理器一样,为@ExceptionHandler注解的方法注入所有默认参数。也就是说,你可以在该方法中添加异常、负载、元数据(MetaData)及其他可选参数

聚合异常处理器
class GiftCard {

    // 为简洁起见,省略了状态、命令处理器和事件溯源处理器

    @ExceptionHandler
    public void handleAll(Exception exception) {
        // 以通用方式处理该组件内抛出的所有异常
    }

    @ExceptionHandler
    public void handleIssueCardExceptions(IssueCardCommand command) {
        // 处理该组件内 IssueCardCommand 处理器抛出的所有异常
    }

    @ExceptionHandler(payloadType = IssueCardCommand.class)
    public void handleIssueCardExceptions() {
        // 处理该组件内 IssueCardCommand 处理器抛出的所有异常
    }

    @ExceptionHandler
    public void handleIllegalStateExceptions(IllegalStateException exception) {
        // 处理该组件内抛出的所有 IllegalStateException
    }

    @ExceptionHandler(resultType = IllegalStateException.class)
    public void handleIllegalStateExceptions(Exception exception) {
        // 处理该组件内抛出的所有 IllegalStateException
    }

    @ExceptionHandler
    public void handleIllegalStateExceptionsFromIssueCard(IssueCardCommand command,
                                                          IllegalStateException exception) {
        // 处理该组件内 IssueCardCommand 处理器抛出的所有 IllegalStateException
    }

    @ExceptionHandler(resultType = IllegalStateException.class, payloadType = IssueCardCommand.class)
    public void handleIllegalStateExceptionsFromIssueCard() {
        // 处理该组件内 IssueCardCommand 处理器抛出的所有 IllegalStateException
    }
}
聚合构造函数的异常处理

@ExceptionHandler注解的方法需要依赖已存在的组件实例才能工作。因此,异常处理器不适用于聚合的(命令处理)构造函数

如果你希望以特定方式处理聚合命令处理器可能抛出的异常,建议使用Axon的创建策略

投影器异常处理器
class CardSummaryProjection {

    // 为简洁起见,省略了仓库/服务、事件处理器和查询处理器

    @ExceptionHandler
    public void handleAll(Exception exception) {
        // 以通用方式处理该组件内抛出的所有异常
    }

    @ExceptionHandler
    public void handleFindCardQueryExceptions(FindCardQuery query) {
        // 处理该组件内 FindCardQuery 处理器抛出的所有异常
    }

    @ExceptionHandler(payloadType = FindCardQuery.class)
    public void handleFindCardQueryExceptions() {
        // 处理该组件内 FindCardQuery 处理器抛出的所有异常
    }

    @ExceptionHandler
    public void handleIllegalArgumentExceptions(IllegalArgumentException exception) {
        // 处理该组件内抛出的所有 IllegalArgumentException
    }

    @ExceptionHandler(resultType = IllegalArgumentException.class)
    public void handleIllegalArgumentExceptions(Exception exception) {
        // 处理该组件内抛出的所有 IllegalArgumentException
    }

    @ExceptionHandler
    public void handleIllegalArgumentExceptionsFromCardIssued(CardIssuedEvent event,
                                                              IllegalArgumentException exception) {
        // 处理该组件内 CardIssuedEvent 处理器抛出的所有 IllegalArgumentException
    }

    @ExceptionHandler(resultType = IllegalArgumentException.class, payloadType = CardIssuedEvent.class)
    public void handleIllegalArgumentExceptionsFromCardIssued() {
        // 处理该组件内 CardIssuedEvent 处理器抛出的所有 IllegalArgumentException
    }
}

10.异常处理

介绍

在软件开发中,异常处理是一个广为人知的概念。但在分布式应用架构中处理异常,比常规单体应用更具挑战性 尤其是在处理命令(Command)或查询(Query)这类期望返回值的消息时,更需谨慎设计异常的抛出逻辑

HandlerExecutionException

HandlerExecutionException用于标记从消息处理成员(CommandHandler、QueryHandler)抛出的异常。需注意以下核心特性:

  • 适用场景限制:由于Event消息是单向的,事件处理(EventHandler)不会返回任何结果,因此HandlerExecutionException仅应作为命令或查询处理的异常结果返回,不适用于事件处理场景
  • 具体实现类:Axon为命令和查询处理的失败场景,分别提供了更具体的异常实现:CommandExecutionException,QueryExecutionException

分布式场景下的异常处理价值

在分布式应用环境中(例如命令处理应用与查询处理应用分离部署),HandlerExecutionException的价值更为突出:

  • 类依赖解耦:由于应用拆分,无法保证两个应用能访问相同的自定义异常类(若直接抛出自定义异常,跨应用传输时可能因类不存在导致反序列化失败)。为支持这种解耦,Axon会将命令/查询处理产生的任何异常泛化为标准的HandlerExecutionException及其子类,避免类依赖问题
  • 异常详情保留:为了在分布式场景下仍能基于异常类型执行条件逻辑(如根据不同异常类型返回不同错误码),HandlerExecutionException支持携带异常详情(details)。因此,建议在命令/查询处理失败时,主动抛出CommandExecutionException/QueryExecutionException并传入必要的详情信息(如错误码、业务描述)
  • 通用异常包装:可通过实现拦截器(Interceptor),将自定义异常自动包装为CommandExecutionException/QueryExecutionException,实现异常处理的通用性

与@ExceptionHandler的区别

Axon Framework支持通过@ExceptionHandler注解的方法,对异常响应逻辑进行更精细的控制。该注解的核心定位与特性如下:

  1. 本质是特殊拦截器:@ExceptionHandler本质是一种专门响应异常结果的MessageHandlerInterceptor(消息处理器拦截器)
  2. 作用范围限制:标注@ExceptionHandler的方法仅处理同一类中消息处理函数(如@CommandHandler、@QueryHandler、@EventHandler)抛出的异常,不跨类生效

11.超时机制

介绍

Axon Framework围绕消息的异步处理构建,为避免消息处理陷入无限阻塞,Axon提供了一套超时机制,可对消息处理器调用或整个工作单元处理流程设置超时时间

超时与告警机制在Axon4.11版本中引入

核心概念

两种超时机制(处理器超时、工作单元超时)均基于以下核心配置项工作,以控制执行时长和日志告警逻辑:

  1. timeoutMs:执行超时阈值(毫秒)超过该时间后,处理流程会被中断
  2. warningThreshold:告警阈值(毫秒)超过该时间后,开始记录告警日志
  3. warningInterval:告警间隔(毫秒)超过warningThreshold后,每间隔该时间重复记录一次告警日志

默认行为

对于长时间运行的处理器或工作单元,会按以下流程触发行为:

  1. 当执行时间超过warningThreshold时,记录第一条告警日志
  2. 之后每间隔warningInterval,记录一次重复告警日志
  3. 当执行时间超过timeoutMs时,中断处理器或事务的执行

所有告警和超时日志均以WARN级别记录,日志内容包含以下关键信息,便于问题排查:

  1. 处理器/事务组件名称(如EventProcessor名称、CommandBus标识)及对应的消息
  2. 当前已执行时长
  3. 距离超时剩余的时间
  4. 超时触发时的线程堆栈跟踪(stack trace)从超时开始位置生成,便于定位阻塞代码
2025-02-12T20:01:20.795Z  WARN 68040 --- [playground-correlation] [ axon-janitor-0] axon-janitor                             : Message [io.axoniq.playground.publisher.MyContinuousEvent] for handler [io.axoniq.playground.publisher.PersistentStreamEventProcessor] is taking a long time to process. Current time: [5000ms]. Will be interrupted in [5000ms].
Stacktrace of current thread:
java.base/java.lang.Thread.sleep0(Native Method)
java.base/java.lang.Thread.sleep(Thread.java:509)
io.axoniq.playground.publisher.PersistentStreamEventProcessor.handle(PersistentStreamEventProcessor.kt:16)
java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
/** 为简洁起见,省略部分堆栈跟踪 **/
org.axonframework.messaging.timeout.TimeoutWrappedMessageHandlingMember.handle(TimeoutWrappedMessageHandlingMember.java:61)
核心价值
  1. 调试与监控:快速定位长时间运行的处理器,识别性能瓶颈
  2. 避免阻塞:防止异常流程(如HTTP调用无响应、数据库死锁)阻塞后续消息处理,保障系统稳定性
默认配置

为避免影响现有用户的业务流程,默认配置采用较高阈值:长时间运行的处理器/工作单元:10秒后开始记录告警日志,30秒后触发超时中断

禁用

SpringBoot禁用方式如下:

# 完全禁用所有超时与告警
axon:
  timeout:
    enabled: false

Axon在非Spring Boot环境中默认不激活超时配置,因此无需额外操作,只要不注册下文所述的超时配置类,即可禁用超时机制

处理器超时
介绍

处理器指Axon中标注了@CommandHandler、@EventHandler、@QueryHandler 或 @DeadlineHandler的方法(即消息处理方法)。处理器超时机制可对所有消息处理器设置超时,并支持按消息类型(命令、事件、查询、截止时间)配置独立参数

Spring Boot 应用的全局配置

Spring Boot环境下,所有消息处理器的默认超时为30秒,默认10秒后开始每1秒记录一次告警。可通过以下配置项自定义:

axon:
  timeout:
    handler:
      commands:
        timeout-ms: 20000
        warning-interval-ms: 1000
        warning-threshold-ms: 5000
      deadlines:
        timeout-ms: 20000
        warning-interval-ms: 1000
        warning-threshold-ms: 5000
      events:
        timeout-ms: 20000
        warning-interval-ms: 1000
        warning-threshold-ms: 5000
      queries:
        timeout-ms: 20000
        warning-interval-ms: 1000
        warning-threshold-ms: 5000
非SpringBoot应用

非Spring Boot环境默认无超时配置,需通过ConfigurerModule手动注册,示例如下:

public class MyTimeoutConfigurerModule implements ConfigurerModule {

    @Override
    public void configureModule(@NotNull Configurer configurer) {
        // 1. 创建处理器超时配置对象
        HandlerTimeoutConfiguration config = new HandlerTimeoutConfiguration();
        // 示例:设置 @EventHandler 超时为 30秒(其他消息类型可类似配置)
        config.getEvents().setTimeoutMs(30000);
        config.getEvents().setWarningThresholdMs(25000); // 25秒后开始告警
        config.getEvents().setWarningIntervalMs(1000);  // 每1秒告警一次

        // 2. 注册超时处理器增强器(使配置生效)
        configurer.registerHandlerEnhancerDefinition(
            c -> new HandlerTimeoutHandlerEnhancerDefinition(config)
        );
    }
}
@MessageHandlerTimeout注解

通过@MessageHandlerTimeout注解,可在单个消息处理器方法上覆盖全局配置,为特定方法设置专属超时(适用于 “已知执行速度更快/更慢” 的处理器)。示例如下:

class MyEventProcessor {
    // @EventHandler 方法:超时10秒,5秒后开始每1秒告警
    @EventHandler
    @MessageHandlerTimeout(
        timeoutMs = 10000,        // 超时阈值:10秒
        warningThresholdMs = 5000,// 告警阈值:5秒
        warningIntervalMs = 1000  // 告警间隔:1秒
    )
    public void handle(Object event) throws InterruptedException {
        // 模拟长时间处理(19秒,会触发超时)
        Thread.sleep(19000);
    }
}

禁用单个处理器的超时/告警:

  1. 若需禁用某处理器的超时:将全局配置或注解的timeoutMs设为-1
  2. 若需禁用某处理器的告警:将全局配置或注解的warningThresholdMs设为 -1
工作单元超时
介绍

UnitOfWork是消息处理的上下文,包含资源加载(如加载命令对应的聚合根)→ 处理器调用 → UnitOfWork提交的完整流程。与“仅作用于处理器调用的处理器超时不同,工作单元超时作用于整个消息处理流程,可针对CommandBus、QueryBus、EventProcessor等组件分别配置

SpringBoot配置

Spring Boot环境下,所有工作单元的默认超时为60秒,默认10秒后开始每1秒记录一次告警。支持 “全局配置” 和 “组件级配置”(组件级配置优先级更高),示例如下:

axon:
  timeout:
    transaction:
      # CommandBus 超时配置
      command-bus:
        timeout-ms: 20000
        warning-interval-ms: 1000
        warning-threshold-ms: 10000
      # DeadlineManager 超时配置
      deadline:
        timeout-ms: 20000
        warning-interval-ms: 1000
        warning-threshold-ms: 10000
      event-processor:
        # 特定 EventProcessor 超时配置(优先级最高)
        my-processor:
          timeout-ms: 2000
          warning-interval-ms: 100
          warning-threshold-ms: 1000
      # 所有 EventProcessor 全局配置(无组件级配置时生效)    
      event-processors:
        timeout-ms: 20000
        warning-interval-ms: 1000
        warning-threshold-ms: 10000
      # QueryBus 超时配置
      query:
        timeout-ms: 20000
        warning-interval-ms: 1000
        warning-threshold-ms: 10000
非SpringBoot配置

非Spring Boot环境需手动注册UnitOfWorkTimeoutInterceptor到目标组件,示例如下:

public class MyTimeoutConfigurerModule implements ConfigurerModule {

    @Override
    public void configureModule(@NotNull Configurer configurer) {
        // 1. 为 EventProcessor 注册工作单元超时拦截器
        configurer.eventProcessing()
                  .registerDefaultHandlerInterceptor((c, processorName) -> 
                      new UnitOfWorkTimeoutInterceptor(
                          "EventProcessor " + processorName, // 组件标识(日志中显示)
                          30000,         // 超时阈值:30秒
                          25000,         // 告警阈值:25秒
                          1000           // 告警间隔:1秒
                      )
                  );

        // 2. 为 CommandBus 注册工作单元超时拦截器(启动时执行)
        configurer.onStart(Integer.MIN_VALUE, () -> {
            CommandBus commandBus = configurer.buildConfiguration().commandBus();
            commandBus.registerHandlerInterceptor(
                new UnitOfWorkTimeoutInterceptor(
                    "CommandBus",    // 组件标识
                    30000,          // 超时阈值:30秒
                    25000,          // 告警阈值:25秒
                    1000            // 告警间隔:1秒
                )
            );

            // (可选)为 QueryBus、DeadlineManager 注册类似拦截器
            QueryBus queryBus = configurer.buildConfiguration().queryBus();
            queryBus.registerHandlerInterceptor(/* 类似 CommandBus 配置 */);
        });
    }
}