SpringBoot读取SNS转SQS消息时反序列化失败问题求助
我一眼就看出问题所在了:SNS转发到SQS的消息是JSON嵌套字符串的结构——外层是SNS的通知JSON,其中Message字段的值本身又是一个转义后的JSON字符串(也就是你要解析的PushEvent内容)。但你的SqsMessage类直接把message字段定义成PushEvent<?>类型,Jackson会默认把这个字符串当成普通文本去转成PushEvent对象,可PushEvent并没有接收字符串参数的构造方法,这就直接导致了反序列化失败。
下面给你几种可行的解决方案,按需选择:
方案1:先接收字符串,手动解析(最直接易维护)
先把Message字段当成字符串接收,再在业务代码里手动反序列化成PushEvent,逻辑清晰,不容易出问题:
第一步:修改SqsMessage类
把message字段的类型从PushEvent<?>改成String:
public class SqsMessage { @JsonProperty("MessageId") String messageId; @JsonProperty("Message") String message; // 改为String类型接收嵌套的JSON字符串 public SqsMessage() {} public SqsMessage(String messageId, String message) { this.messageId = messageId; this.message = message; } // getter和setter同步更新类型 public String getMessageId() { return messageId; } public void setMessageId(String messageId) { this.messageId = messageId; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } }
第二步:修改监听器代码,手动解析PushEvent
注入SpringBoot默认提供的ObjectMapper,在监听器里完成字符串到PushEvent的转换:
@Autowired private ObjectMapper objectMapper; @SqsListener(QUEUE_NAME) public void receiveMessage(SqsMessage message, @Header("SenderId") String senderId) { logger.info("Service A received messageId: {}", message.getMessageId()); // 手动解析嵌套的JSON字符串 try { PushEvent<?> pushEvent = objectMapper.readValue(message.getMessage(), PushEvent.class); logger.info("Service A received author: {}", pushEvent.getAuthor()); logger.info("Service A received error: {}", pushEvent.getError()); } catch (JsonProcessingException e) { logger.error("Failed to parse PushEvent from message content", e); // 这里可以添加异常处理逻辑,比如消息重试、死信队列投递等 } logger.info("Service A senderId: {}", senderId); }
方案2:用Jackson注解自动处理嵌套JSON
如果你不想手动写解析逻辑,可以通过Jackson的注解让框架自动处理这个嵌套字符串:
方式2.1:给PushEvent添加字符串参数的构造方法
给PushEvent加一个接收字符串的构造方法,并用@JsonCreator标记,让Jackson知道用这个方法来处理字符串输入:
@Data @Builder @AllArgsConstructor @NoArgsConstructor @ToString public class PushEvent<T> { String author; String error; // 新增字符串参数的构造方法,用于Jackson反序列化 @JsonCreator public PushEvent(String jsonString) throws JsonProcessingException { ObjectMapper mapper = new ObjectMapper(); PushEvent<?> event = mapper.readValue(jsonString, PushEvent.class); this.author = event.getAuthor(); this.error = event.getError(); } }
注意:如果PushEvent的泛型T需要实际使用,你需要调整这个构造方法的泛型处理逻辑。
方式2.2:自定义反序列化器
如果构造方法的方式不够灵活,可以写一个专属的反序列化器:
- 编写反序列化器类:
public class PushEventDeserializer extends JsonDeserializer<PushEvent<?>> { private final ObjectMapper objectMapper = new ObjectMapper(); @Override public PushEvent<?> deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { // 读取嵌套的JSON字符串,再转成PushEvent对象 String jsonString = p.getText(); return objectMapper.readValue(jsonString, PushEvent.class); } }
- 在
SqsMessage的message字段上添加注解,指定使用自定义反序列化器:
public class SqsMessage { @JsonProperty("MessageId") String messageId; @JsonProperty("Message") @JsonDeserialize(using = PushEventDeserializer.class) // 指定反序列化器 PushEvent<?> message; // 构造方法、getter、setter保持不变 }
方案3:优化Spring Cloud AWS的消息转换器配置
你的当前配置里重复添加了MappingJackson2MessageConverter,可以优化一下,避免冗余配置(这个是辅助优化,需要配合上面的方案1或2一起使用):
@Configuration public class SpringCloudAwsConfig { @Bean public QueueMessageHandlerFactory queueMessageHandlerFactory() { QueueMessageHandlerFactory factory = new QueueMessageHandlerFactory(); MappingJackson2MessageConverter messageConverter = new MappingJackson2MessageConverter(); messageConverter.setStrictContentTypeMatch(false); // 只需要设置一次参数解析器即可,无需重复添加消息转换器 factory.setArgumentResolvers(Collections.singletonList(new PayloadArgumentResolver(messageConverter))); return factory; } // 其他Bean(queueMessagingTemplate、notificationMessagingTemplate)保持不变 }
再帮你梳理下错误根源
从错误日志里的消息结构能清楚看到问题:
{ "Type" : "Notification", "MessageId" : "56639e45-586e-5281-9204-cbeed1890b81", "TopicArn" : "arn:aws:sns:eu-west-1:xxxxxxxxxxxx:eventNow", "Message" : "{\"author\":\"Peter Smith\",\"error\":\"error of test\"}", ... }
这里的Message值是一个转义后的JSON字符串,而不是直接的JSON对象。Jackson默认会把它当成普通字符串处理,但你的SqsMessage里把message定义成PushEvent类型,Jackson就会尝试把字符串转成PushEvent,但找不到对应的构造方法,于是抛出了MismatchedInputException。
内容的提问来源于stack exchange,提问作者Stéphane GRILLON

