【RocketMQ消费 消息时对象序列化问题分析与解决】

一、问题背景

在 RocketMQ 消费消息时,默认会将消息内容转换成 String 类型 进行传输。原本是一个map结构,接收是String 类型导致ENGINE_RESPONSE有很多转义字符。阿里云的RocketMQ消息,内容如下:

{
  "BUSINESS_ID": 12,
  "result": "PASS",
  "CUSTOMER_ID": 123456,
  "type": "A",
  "ENGINE_RESPONSE": "{\"data\":{\"ac\":969,\"bd\":0.06,\"type\":\"A\",\"score\":768.0,\"amt\":1,\"choice\":\"3,6\",\"cardType\":\"test\",\"eventID\":\"db4c3ae6-4114-4ab8-8bd4-a00b9aeb3c47\",\"orderID\":12}}"
}

消息的ENGINE_RESPONSE字段,内部嵌套了一个JSON的字符串,而不是直接的JSON对象。直接用ObjectMapper 反序列化时进行反序列化解析的时候会报类型不匹配解析失败,导致消费失败。

二、问题原因

实体类:

@Data
public class RiskMessage {
    @JsonProperty("BUSINESS_ID")
    private Long businessId;
    
    private String result;
    
    @JsonProperty("CUSTOMER_ID")
    private Long customerId;
    
    private String type;
    
    @JsonProperty("ENGINE_RESPONSE")
    private EngineResponse engineResponse; 
}

错误:

com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of com.yinshan.arkham.risk.RiskResponse$EngineResponse (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value 

Jackson 试图将字符串 ENGINE_RESPONSE 直接解析为 EngineResponse 对象,但没有适用于字符串的构造方法或工厂方法,导致解析失败。

三、思考与采取措施

Jackson 认为 ENGINE_RESPONSE 是一个普通的 String,但我们的 DTO 里将它映射为 EngineResponse 对象,类型不匹配。

问题分析

Jackson 认为 ENGINE_RESPONSEString,但 DTO 里是 EngineResponse,类型不匹配。还带转义字符。

思考过程

  • 方法 1: 手动去除转义字符
    错误说是String类型,修改类型 EngineResponse 为String ,还要用到其中的其他字段,所以在getEngineResponse里面,先去掉转义字符,然后在用ObjectMapper 解析。
  • String EngineResponse
  • 方法 2:使用 Jackson 自定义反序列化器
    自定义反序列化器 这样就可以直接用EngineResponse类型

四、解决方案

方法 1:手动去除转义字符,在 getEngineResponse() 方法中处理

@JsonProperty("ENGINE_RESPONSE")
private String engineResponseJson; // 原始数据

private EngineResponse engineResponse; // 实际使用的对象
public EngineResponse getEngineResponse() {
        if (engineResponse == null && engineResponseJson != null) {
            try {
    
                String cleanJson = engineResponseJson.replaceAll("\\\\\"", "\"");
                ObjectMapper objectMapper = new ObjectMapper();
                this.engineResponse = objectMapper.readValue(cleanJson, EngineResponse.class);
            } catch (Exception e) {
                throw new RuntimeException("Failed to parse ENGINE_RESPONSE", e);
            }
        }
        return engineResponse;
    }

方法 2:使用 Jackson 自定义反序列化器
直接在 ENGINE_RESPONSE 这个字段上解析 JSON,而不是手动处理转义字符

public class EngineResponseDeserializer extends JsonDeserializer<RiskResponse.EngineResponse> {

	@Override
	public RiskResponse.EngineResponse deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
		ObjectMapper objectMapper = (ObjectMapper) p.getCodec();
		String jsonString = p.getText();
		return objectMapper.readValue(jsonString, RiskResponse.EngineResponse.class);
	}
}
@JsonProperty("ENGINE_RESPONSE")
@JsonDeserialize(using = EngineResponseDeserializer.class)
private EngineResponse engineResponse;

五、总结

问题的关键在于消息体中某些字段是嵌套的 JSON 字符串,而不是直接的 JSON 对象。

对比两种方法

在这里插入图片描述

<think>好的,我现在需要帮助用户理解Spring RocketMQ消费者消费消息消息转换机制。用户之前已经询问过生产者消息转换,现在转向消费者这边。首先,我要回顾之前提供的关于生产者的信息,确保消费者的转换机制能够对应上。 根据用户提供的引用,特别是引用[1]和[2],Spring Cloud Stream使用Binder来处理消息中间件的集,而消费者部分可能涉及到消息的反序列化。用户的问题集中在消费者如何转换消息,所以需要详细说明从RocketMQ消息到Spring Message的转换过程。 首先,消费者消息转换应该包括几个步骤:接收原生RocketMQ消息、提取消息头和负载、反序列化负载为Java对象,以及处理消息确认。需要引用相关的组件,比如`RocketMQMessageListener`和`RocketMQMessageConverter`,这些在之前的回答中可能提到过。同,用户提供的引用[4]提到了消费进度的存储,这可能和消息确认机制有关,需要结合进来。 然后,要确保结构清晰,分步骤说明每个环节。例如,消息接收序列化消息头的处理、消息确认机制,以及扩展点如自定义转换器。需要结合代码示例,如消费者监听器的配置,以及可能的YAML配置,这样用户能更直观地理解。 还要注意用户提到的引用[3],关于事务消息,虽然主要问题消费者,但如果有相关部分需要提及,比如事务消息消费确认,可以简要说明。但重点还是消费者的转换流程。 可能的问题点:如何确保反序列化的正确性?如何处理不同的消息格式?如何自定义转换器?这些都需要在回答中覆盖,同的相关问题也要围绕这些点展开。 最后,检查是否符合之前的格式要求,使用正确的LaTeX格式,添加引用标记,并确保回答结构符合用户提供的示例。需要参考用户给的四个引用,尤其是引用[2]中的StreamBridge和消费者的代码示例,可能涉及到消息的接收和处理流程。 总结:消费者消息转换机制主要涉及接收消息、反序列化负载、处理消息头,以及确认机制。需要详细说明每个步骤,结合代码和配置示例,并引用提供的参考资料来支持回答。</think>#### Spring RocketMQ消费者消息转换机制详解 --- ##### **1. 消息接收序列化流程** 消费者通过**RocketMQ Binder**监听指定Topic,消息转换机制的核心步骤如下: 1. **原生消息接收** RocketMQ消费者从Broker拉取消息,获取原生`org.apache.rocketmq.common.message.Message`对象[^1]。 ```java // RocketMQ原生消息结构示例 public class Message { private String topic; private byte[] body; // 消息体字节数组 private Map<String, String> properties; // 消息属性(包含headers) } ``` 2. **消息体反序列化** Spring Cloud Stream通过`RocketMQMessageConverter`将字节数组`body`反序列化为目标类型。默认支持以下序列化方式: - **文本消息**:直接转换为`String` - **JSON消息**:通过`Jackson`反序列化为POJO ```java // 消费者监听示例 @Bean public Consumer<Message<String>> input() { return message -> { String payload = message.getPayload(); // 反序列化后的字符串 // 处理逻辑 }; } ``` 3. **消息头映射** RocketMQ消息的`properties`字段(如`TAGS`、`KEYS`)会被提取并填充到Spring Message的`headers`中: $$ \text{RocketMQ Message} \xrightarrow{\text{反序列化}} \text{Spring Message} $$ ```java // 获取消息头示例 String tag = message.getHeaders().get(RocketMQHeaders.TAGS, String.class); ``` --- ##### **2. 消费确认机制** Spring RocketMQ消费者支持两种消费模式: - **同步确认(CLUSTERING模式)** 消费功后,消费进度(offset)由Broker统一管理[^4]。 ```yaml spring: cloud: stream: rocketmq: binder: consumer: consumeMode: CONCURRENTLY # 并发消费 messageModel: CLUSTERING # 集群模式 ``` - **本地进度管理(BROADCASTING模式)** 每个消费者独立维护消费进度,适用于广播场景[^4]。 --- ##### **3. 扩展点自定义转换** 1. **自定义反序列化逻辑** 实现`RocketMQMessageConverter`接口,覆盖`toMessage`方法: ```java @Bean public RocketMQMessageConverter customConverter() { return new RocketMQMessageConverter() { @Override public Message<?> toMessage(org.apache.rocketmq.common.message.Message source, MessageHeaders headers) { // 自定义反序列化逻辑 return MessageBuilder.createPayload(parsePayload(source.getBody())) .copyHeaders(headers) .build(); } }; } ``` 2. **消息过滤** 通过`selectorExpression`指定TAG或SQL92表达式过滤消息: ```yaml spring: cloud: stream: bindings: input-in-0: destination: testTopic group: consumerGroup rocketmq: consumer: tags: "tagA || tagB" ``` --- ##### **4. 事务消息的特殊处理** 消费者处理事务消息,需注意: - **幂等性设计**:由于RocketMQ可能重复投递消息(如网络重试),消费者需保证业务逻辑的幂等性。 - **事务回查兼容性**:消费者无需感知事务状态,但需处理可能延迟到达的消息[^3]。 --- #### 相关配置参考 ```yaml spring: cloud: stream: bindings: input-in-0: # 输入通道 destination: testTopic content-type: application/json # 指定消息格式 rocketmq: binder: name-server: 127.0.0.1:9876 ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值