[RabbitMQ] Jackson2JsonMessageConvertor

2021. 11. 3. 11:42 Spring Cloud/RabbitMQ

이번 글에서는 Spring Boot의 Jackson2JsonMessageConverter를 사용해 손쉽게 Object를 JSON Message Format으로 변경해보겠습니다.

1. Message Converter란?

object를 rabbitmq의 message 형식으로 변환해주는 것을 의미합니다.

이전글까지는 Rabbitmq를 통해 메세지를 주고받기 위해, object들을 string 형식으로 변환해야하는 번거로움이 있었습니다. 메세지를 전달할 때 사용하는 RabbitTemplate의 default message convertor(SimpleMessageConverter)가 input data type으로 string을 받기때문입니다.

 

 

따라서 아래와 같이 ObjectMapper를 사용해서 object를 string 형식으로 변환한뒤 rabbitTemplate을 통해 메세지 형식으로 변환한 데이터를 rabbitmq server로 전달했습니다.

    private ObjectMapper objectMapper = new ObjectMapper();

    public void sendDummy(DummyMessage message){
        String s = objectMapper.writeValueAsString(message);
        rabbitTemplate.convertAndSend("exchange", "routingkey", s);
    }

😎 Jackson2JsonMessageConverter 사용전

전체 흐름은 아래와 같습니다.

 

 

2. Jackson2JsonMessageConverter란?

위와 같은 번거로움을 Jackson2JsonMessageConverter를 사용해 해결할 수 있습니다.

Jackson2JsonMessageConveter는 rabbittemplate의 default converter인 simplemessageconverter와는 다르게 input data type으로 object받습니다. 따라서 rabbittemplate의 default converter를 Jackson2JasonMessageConverter로 변경해 사용하면 더이상 object를 string으로 변환하지 않아도 됩니다.

 

 

또한, Jackson2JsonMessageConveter로 변환한 메세지는 body의 형식이 JSON message 형식으로 변환됩니다.

😎Jackson2JsonMessageConverter 사용후

전체 흐름은 아래와 같습니다.

 

 

Jackson2JsonMessageConverter를 사용하기위해선 rabbittemplate의 default message converter를 변경해야합니다. Produer와 Consumer 패키지 모두 변경해야 사용할 수 있습니다.

이를 위해 아래와 같이 @Configuration을 선언한 config 파일을 생성합니다.

@Configuration
public class RabbitmqSchemaConfig {

    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory){
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        rabbitTemplate.setMessageConverter(jsonMessageConverter());

        return rabbitTemplate;
    }

    @Bean
    public Jackson2JsonMessageConverter jsonMessageConverter(){
        return new Jackson2JsonMessageConverter();
    }

}

위의 소스를 해석해보면, @Bean으로 생성하는 RabbitTemplate의 message conveter를 setMessageConverter()를 통해 Jackson2JsonMessageConvert로 설정합니다. 여기서 Jackson2JsonMessageConvert또한 @Bean으로 생성한것을 사용합니다.

자, 이제 더이상 ObjectMapper를 사용할 필요가 없어졌습니다. 이제부터는 아래와 같이 소스를 변경해 사용하면 됩니다.

변경후

    public void sendDummy(DummyMessage message){
        rabbitTemplate.convertAndSend("x.dummy", "", message);
    }

변경전

    private ObjectMapper objectMapper = new ObjectMapper();

    public void sendDummy(DummyMessage message){
        String s = objectMapper.writeValueAsString(message);
        rabbitTemplate.convertAndSend("exchange", "routingkey", s);
    }

3. Message TypeID

위와 같이 Jackson2JsonMessageConverter를 사용하면 consumer에서는 어떻게 메세지를 object로 deserailize 할 수 있을까요?

정답은 Message의 TypeId를 사용하는 것입니다. Jackson2JsonMessageConverter를 사용해 메세지를 보내면 메세지의 header에는 전체 package name을 포함한 entity class의 이름이 포함되어있습니다. 따라서 이를 활용해 이전의 object 형식으로 deserialize 할 수 있습니다.

 

 

여기서 주의사항이 있습니다.

Jackson2JsonMessageConverter는 message typeid의 full name을 사용해 deserialize할 object를 찾기 때문에, 만약 producer와 consumer의 entity가 위치한 package명이 다를경우 에러가 발생합니다.

예를 들어 아래와 같이 package명이 다를경우

 

 

producer와 consumer project의 entitiy가 위치한 package명을 통일 시켜줘야합니다.

 

 

이후에는 아래와 같이 consumer도 동일하게 ObjectMapper없이 메세지를 읽어 object로 deserialize 할 수 있습니다.

변경후

    private static final Logger Log = LoggerFactory.getLogger(DummyConsumer.class);

    @RabbitListener(queues = "q.dummy")
    public void listen(DummyMessage message){
        Log.info("{}", message);
    }

변경전

    private static final Logger Log = LoggerFactory.getLogger(DummyConsumer.class);
    private ObjectMapper objectMapper = new ObjectMapper();

    @RabbitListener(queues = "q.dummy")
    public void listen(String message){
        DummyMessage message = objectMapper.readValue(message, DummyMessage.class);
        Log.info("{}", message);
    }

4. SimpleRabbitListenerContainerFactory

만약 이미 배포된 프로젝트이기 때문에, package 명을 변경할 수 없는 경우 아래와 같은 config 파일을 사용해 로직을 구성할 수 있습니다.

  @Bean(name = "rabbitListenerContainerFactory")
    public SimpleRabbitListenerContainerFactory simpleRabbitListenerContainerFactory(
            SimpleRabbitListenerContainerFactoryConfigurer configurer, ConnectionFactory connectionFactory) {
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        configurer.configure(factory, connectionFactory);

        factory.setAfterReceivePostProcessors(new MessagePostProcessor() {

            @Override
            public Message postProcessMessage(Message message) throws AmqpException {
                var type = message.getMessageProperties().getHeaders().get("type").toString();
                String typeId = null;

                if (StringUtils.equalsIgnoreCase(type, "invoice.paid")) {
                    typeId = InvoicePaidMessage.class.getName();
                } else if (StringUtils.equalsIgnoreCase(type, "invoice.created")) {
                    typeId = InvoiceCreatedMessage.class.getName();
                }

                Optional.ofNullable(typeId).ifPresent(t -> message.getMessageProperties().setHeader("__TypeId__", t));

                return message;
            }

        });

        return factory;
    }

참고 자료 : https://www.udemy.com/course/rabbitmq-java-spring-boot-for-system-integration/

출처 : https://minholee93.tistory.com/entry/RabbitMQ-Jackson2JsonMessageConvertor