문제
Failed to convert Message content
org.springframework.amqp.support.converter.MessageConversionException: Failed to convert Message content
at org.springframework.amqp.support.converter.AbstractJackson2MessageConverter.createMessage(AbstractJackson2MessageConverter.java:463)
at org.springframework.amqp.support.converter.AbstractMessageConverter.toMessage(AbstractMessageConverter.java:70)
at org.springframework.amqp.support.converter.AbstractMessageConverter.toMessage(AbstractMessageConverter.java:58)
at org.springframework.amqp.rabbit.core.RabbitTemplate.convertMessageIfNecessary(RabbitTemplate.java:1892)
at org.springframework.amqp.rabbit.core.RabbitTemplate.convertAndSend(RabbitTemplate.java:1188)
at org.springframework.amqp.rabbit.core.RabbitTemplate.convertAndSend(RabbitTemplate.java:1181)
rabbitmq를 사용하다 message convert가 실패했다고 했다.
원인 찾아보기
rabbitmq 설정파일에서 AbstractJackson2MessageConverter를 설정해주었다.
@Configuration
public class RabbitMqConfig {
...
/**
* RabbitTemplate을 생성하여 반환
*
* @param connectionFactory RabbitMQ와의 연결을 위한 ConnectionFactory 객체
* @return RabbitTemplate 객체
*/
@Bean
public RabbitTemplate rabbitTemplate(
ConnectionFactory connectionFactory, MessageConverter messageConverter) {
RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
// JSON 형식의 메시지를 직렬화하고 역직렬할 수 있도록 설정
rabbitTemplate.setMessageConverter(messageConverter);
return rabbitTemplate;
}
/**
* Jackson 라이브러리를 사용하여 메시지를 JSON 형식으로 변환하는 MessageConverter 빈을 생성
*
* @return MessageConverter 객체
*/
@Bean
public MessageConverter jackson2JsonMessageConverter() {
return new Jackson2JsonMessageConverter();
}
}
여기서 Jackson2JsonMessageConverter는 Spring AMQP에서 메시지를 JSON 형식으로 직렬화하고 역직렬화하는 데 사용되는 메시지 컨버터다.
해당 컨버터를 사용하면 Jackson 라이브러리를 기반으로 해서, 메시지를 Java 객체로 변환하고 Java 객체를 메시지로 변환할 수 있다.
해당 컨버터는 따로 추가 설정을 해주지 않으면 인터페이스는변환해주지 않는다.
코드를 유연하게 작성하고 싶어 아래와 같이 메세지를 interface로 사용해봤는데 위의 에러가 떴다😅
public interface Notifiable {
String getNotificationMessage();
}
@RabbitListener(queues = "${큐이름}")
public void createNotification(Notifiable notifiable) {
log.info("[NotificationReceiver]createNotificationByReservation <{}>", notifiable);
// 로직
}
해결하기
구체클래스 사용하기
@NoArgsConstructor
@AllArgsConstructor
@Getter
@Builder
public class TestMessage {
String arg;
LocalDateTime date;
}
주의할 점은 getter와 기본생성자가 있어야 된다.
추가 설정을 통해 인터페이스 사용하기
찾아보다 스프링 공식 문서에서 interface를 사용하는 방법이 있었다.
1. useProjectionForInterfaces
먼저
이 기능을 활성화하려면 useProjectionForInterfaces를 true로 설정해야한다.
메시지 컨버터에서 useProjectionForInterfaces를 true로 설정면된다.
@Bean
public MessageConverter jackson2JsonMessageConverter() {
Jackson2JsonMessageConverter jackson2JsonMessageConverter = new Jackson2JsonMessageConverter();
jackson2JsonMessageConverter.setUseProjectionForInterfaces(true); // 추가
return jackson2JsonMessageConverter;
}
2. dependency
그리고 클래스 경로에 spring-data:spring-data-commons 및 com.jayway.jsonpath:json-path를 추가해야한다.
implementation 'org.springframework.data:spring-data-commons:3.2.5'
implementation 'com.jayway.jsonpath:json-path:2.9.0'
- org.springframework.data:spring-data-commons은 jpa 의존성에 포함되어 있는 것으로 확인해 추가하진 않았다.
이렇게 설정을 해주면 @RabbitListener 메서드의 매개변수로 사용될 때, 인터페이스 유형이 일반적으로 변환기에 자동으로 전달된다.
참고로 com.jayway.jsonpath:json-path 가 없다면 아래와 같은 Exception이 발생한다.
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jackson2JsonMessageConverter' defined in class path resource [com/zb/deuggeun/common/config/RabbitMqConfig.class]: Failed to instantiate [org.springframework.amqp.support.converter.MessageConverter]: Factory method 'jackson2JsonMessageConverter' threw exception with message: com/jayway/jsonpath/spi/mapper/MappingProvider
Test
public interface TestMessageInterface {
// @JsonPath({"$.username", "$.user.name"})
String getUsername();
}
@Slf4j
@Service
@RequiredArgsConstructor
public class TestReceiver { // 메시지 구독
private CountDownLatch latch = new CountDownLatch(1);
@RabbitListener(queues = "test")
public void test(TestMessageInterface testMessage) {
log.info("[RabbitMQ][Receiver]test <{}>", testMessage.getUsername());
latch.countDown();
}
public CountDownLatch getLatch() {
return latch;
}
}
@Slf4j
@Service
@RequiredArgsConstructor
public class TestPublisher implements CommandLineRunner { // 발행
private final RabbitTemplate rabbitTemplate;
private final TestReceiver receiver;
private static final int TIME_OUT = 10_000;
@Override // test용
public void run(String... args) throws Exception {
log.info("[RabbitMQ][Runner]Sending message...");
// rabbitTemplate.convertAndSend("test", "test",
// TestMessage.builder().arg("aa").date(LocalDateTime.now()).build());
rabbitTemplate.convertAndSend("test", "test",
new TestMessageInterface() {
@Override
public String getUsername() {
return "aa";
}
});
receiver.getLatch().await(TIME_OUT, TimeUnit.MILLISECONDS);
}
}
참고했던 곳
CustomMessage record 이용했던 곳: https://thepracticaldeveloper.com/produce-and-consume-json-messages-with-spring-boot-amqp/
spring 공식 문서: https://docs.spring.io/spring-amqp/reference/amqp/message-converters.html#data-projection
'spring > spring' 카테고리의 다른 글
[Spring][MSA] Spring에서 MSA (0) | 2024.08.30 |
---|---|
[Spring][RabbitMQ] 설정하고 실행해보기 (0) | 2024.04.22 |
[Spring][Exception] Controller Ambiguous mapping (0) | 2024.04.15 |
[Spring][스프링 부트 핵심 가이드] 스프링 시큐리티: 서비스의 인증과 권한 부여 (0) | 2024.03.13 |
[Spring][스프링 부트 핵심 가이드] 액추에이터 활용하기 (0) | 2024.03.06 |