Axon向同一聚合发送多条命令仅首条被处理异常排查
问题描述
使用Axon框架向同一个聚合(Aggregate)发送多条命令时,仅第一条命令可被正常处理,第二条命令发送后抛出如下异常:
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is org.axonframework.commandhandling.CommandExecutionException: Cannot invoke "Object.hashCode()" because "key" is null
相关实现代码
命令发送服务方法
public void maakAanvraag() { UUID aanvraagId = UUID.randomUUID(); commandGateway.sendAndWait( VerwerkAanvraag.builder() .aanvraagId(aanvraagId) .build() ); commandGateway.sendAndWait( VerwerkPersoonsgegevensVastgesteld.builder() .aanvraagId(aanvraagId) .build() ); commandGateway.sendAndWait( VerwerkOrganisatiegegevensVastgesteld.builder() .aanvraagId(aanvraagId) .organisatieId(organisatieView.getOrganisatieId()) .rolOrganisatie(rolOrganisatie) .build() ); commandGateway.sendAndWait( VerwerkBeperkingErkenningsdoelGematcht.builder() .aanvraagId(aanvraagId) .build()); }
聚合类代码
@Aggregate @Getter @NoArgsConstructor public class Aanvraag { public static final String META_DATA_ZAAKNUMMER = "aanvraag_zaaknummer"; @AggregateIdentifier private UUID aanvraagId; @CommandHandler public Aanvraag(VerwerkAanvraag command) { AanvraagGeregistreerd aanvraagGeregistreerd = AanvraagGeregistreerd.builder() .aanvraagId(command.getAanvraagId()) .build(); apply(aanvraagGeregistreerd, MetaData.with(META_DATA_ZAAKNUMMER, "123456789")); } @EventSourcingHandler public void on(AanvraagGeregistreerd event) { aanvraagId = event.getAanvraagId(); } @CommandHandler public void verwerkOrganisatiegegevensVastgesteld(VerwerkOrganisatiegegevensVastgesteld command) { OrganisatiegegevensVastgesteld persoonsgegevensVastgesteld = OrganisatiegegevensVastgesteld.builder() .aanvraagId(command.getAanvraagId()) .build(); apply(persoonsgegevensVastgesteld); } @EventSourcingHandler public void on(OrganisatiegegevensVastgesteld event) { aanvraagId = event.getAanvraagId(); } @CommandHandler public void verwerkPersoonsgegevensVastgesteld(VerwerkPersoonsgegevensVastgesteld command) { PersoonsgegevensVastgesteld persoonsgegevensVastgesteld = PersoonsgegevensVastgesteld.builder() .aanvraagId(command.getAanvraagId()) .build(); apply(persoonsgegevensVastgesteld); } @EventSourcingHandler public void on(PersoonsgegevensVastgesteld event) { aanvraagId = event.getAanvraagId(); } @CommandHandler public void verwerkBeperkingErkenningsdoelGematcht(VerwerkBeperkingErkenningsdoelGematcht command) { BeperkingErkenningsdoelGematcht beperkingErkenningsdoelGematcht = BeperkingErkenningsdoelGematcht.builder() .aanvraagId(command.getAanvraagId()) .build(); apply(beperkingErkenningsdoelGematcht); } @EventSourcingHandler public void on(BeperkingErkenningsdoelGematcht event) { aanvraagId = event.getAanvraagId(); } }
运行环境
- Spring Boot版本:2.6.6
- 依赖版本:axon-spring-boot-starter 4.5.9
- JDK版本:Java Temurin 17.0.3
问题原因与修复方案
该问题不属于配置错误,是实现逻辑遗漏了必要的注解标识:
- 聚合构造函数上的
@CommandHandler用于创建全新的聚合实例,不需要路由到已存在的聚合,因此第一条命令可以正常执行。 - 其余三个
@CommandHandler标注的方法用于操作已经存在的聚合实例,Axon需要从入参命令中提取聚合ID,才能将命令路由到正确的聚合实例。当前三个命令类中承载聚合ID的aanvraagId字段没有添加@TargetAggregateIdentifier注解,Axon无法提取到路由用的聚合ID,将null作为key传入内部存储结构触发hashCode计算,最终抛出空指针异常。
修复方式:
给VerwerkPersoonsgegevensVastgesteld、VerwerkOrganisatiegegevensVastgesteld、VerwerkBeperkingErkenningsdoelGematcht三个命令类中的aanvraagId字段添加org.axonframework.modelling.command.TargetAggregateIdentifier注解即可,示例如下:
// 以VerwerkPersoonsgegevensVastgesteld为例,其余两个命令类修改逻辑一致 @Builder public class VerwerkPersoonsgegevensVastgesteld { @TargetAggregateIdentifier private UUID aanvraagId; // 其余业务字段保持不变 }
如果不想修改命令类定义,也可以直接在聚合内的CommandHandler方法的命令参数上添加该注解,效果一致。
内容的提问来源于stack exchange,提问作者Jan
相关产品推荐
相关产品推荐

