问题描述
(不想看原因的可以直接跳到解决方式)
最近使用RabbitMQ的时候 在测试一个接口时发现一个小小的问题
利用 RabbitTemplate 进行发送消息的时候 在发送消息之前将其json序列化 然后发送 代码如下
/**
* @description: 持久化消息发送者
* @date: 2024/9/7 15:20
* @version: 1.0
*/
@Component
public class PersistentProducer implements MessageProducer<PersistentMessage> {
@Resource
private RabbitTemplate rabbitTemplate;
@Resource
private ObjectMapper objectMapper;
@Override
public void sendMessage(PersistentMessage message, long delay) {
try {
if(delay==0){
// final String persistentJSON = JSONWithTypeUtil.writeValue(message);
final String persistentJSON = objectMapper.writeValueAsString(message);
final Message build = MessageBuilder.withBody(persistentJSON.getBytes(StandardCharsets.UTF_8)).build();
final int messageType = message.getMessageType();
String messageRouterKey = "";
switch (messageType){
case PersistentMessage.PERSISTENT_TYPE_THUMB:
messageRouterKey = RouterKeyType.PERSISTENT_USER_THUMB_KEY;
break;
case PersistentMessage.PERSISTENT_TYPE_FAVORITE:
messageRouterKey = RouterKeyType.PERSISTENT_USER_FAVORITE_KEY;
break;
}
rabbitTemplate.send(TopicType.TOPIC_PERSISTENT,messageRouterKey,
build);
}
} catch (JsonProcessingException e) {
throw new BusinessException(ResultMessageConstants.ILLEGAL_DATA_FORMAT+"持久化流数据JSON化失败");
}
}
}
看起来是一个很简单的场景 就是根据消息类型匹配路由键 然后发送
其中 PersistentMessage 类有一个自定义类型 这个自定义类型使用Object类型来表示 它可能是各种需要持久化的真实数据 但是如果直接使用objectMapper序列化 序列化的结果不会包含Java类型信息 而是使用Map来存储类型中的成员和值 这就是一个隐含的问题
我们看看会导致什么问题
@RabbitListener(queues = QueueType.PERSISTENT_USER_THUMB_QUEUE)
@Transactional(rollbackFor = NeedCallBackException.class)
public void persistentThumbRecord(Channel channel, Message message){
try {
final byte[] messageBody = message.getBody();
final String jsonMessage = new String(messageBody, StandardCharsets.UTF_8);
final PersistentMessage persistentMessage = objectMapper.readValue(jsonMessage, PersistentMessage.class);
if (checkMessage(persistentMessage)) {
SubstanceOperationReq req = (SubstanceOperationReq) persistentMessage.getPersistentData();
//持久化点赞操作 由于涉及多个数据库操作 启用事务 下面意义相同
boolean flag=substanceOperationRepository.thumbSubstance(req.getSubstanceId(), req.getUserId());
substanceRepository.thumbSubstance(req.getSubstanceId(),flag);
}
} catch (JsonProcessingException e) {
channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
throw new BusinessException(ResultMessageConstants.ILLEGAL_DATA_FORMAT+"在持久化点赞记录反JSON序列化时失败");
}
}
private boolean checkMessage(PersistentMessage persistentMessage) throws JsonProcessingException {
//判空 非空时进行消费
if(persistentMessage != null) {
final Object persistentData = persistentMessage.getPersistentData();
if (persistentData !=null) {
if(!(persistentData instanceof SubstanceOperationReq)){
throw new BusinessException(ResultMessageConstants.ILLEGAL_DATA_FORMAT+"无法进行持久化的数据格式"
+"请确认需要内容操作相关持久化操作传递的消息继承于 SubstanceOperationReq.class");
}
return true;
}
}
return false;
}
可以看到 我们只想要消费指定类型的消息 但是如果使用objectMapper序列化 则从PersistentMessage中取出persistentData时 它是Map类型 肯定不会满足我们想要消费的消息类型的判定 无论我们放入的数据是什么类型 反序列化之后都是Map 这就达不到我们想要根据类型消费的设定了
问题解决
更新一下ObjectMapper配置和实体类的编写即可解决
首先 我们要为需要保留类型的实体类加上如下的注解 在上面的例子中 这个类是 PersistentMessage
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS,include = JsonTypeInfo.As.PROPERTY, property = "@class")
之后为了方便使用 我们将更新配置之后的ObjectMapper封装为工具类
public class JSONWithTypeUtil {
private static ObjectMapper objectMapper = new ObjectMapper()
.setSerializationInclusion(JsonInclude.Include.NON_NULL)
.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
public static String writeValue(Object o) throws JsonProcessingException {
return objectMapper.writeValueAsString(o);
}
public static <T> T readValue(String json,Class<T> clazz) throws JsonProcessingException {
return objectMapper.readValue(json,clazz);
}
}
这样之后 如果不需要保留Java类型 可以注入ObjectMapper 需要保留的 则直接使用工具类即可
对您有帮助的话 点个赞和关注吧~ 预计明天或后天会更新每周更新的前端样式