【SpringBoot实用小知识】SpringBoot中保留类型的JSON序列化方式

适用于MQ和Redis等需要保存JSON信息的场景

问题描述

(不想看原因的可以直接跳到解决方式)
最近使用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 需要保留的 则直接使用工具类即可

对您有帮助的话 点个赞和关注吧~ 预计明天或后天会更新每周更新的前端样式

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值