kagamihogeの日記

kagamihogeの日記です。

spring-amqpでPOJOをバイナリorJSONで送受信する

手順など

pom.xml

コメントアウトの箇所は後述。

 <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.0.RELEASE</version>
        <relativePath /> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>13</maven.compiler.source>
        <maven.compiler.target>13</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
<!--        <dependency> -->
<!--            <groupId>org.springframework.boot</groupId> -->
<!--            <artifactId>spring-boot-starter-json</artifactId> -->
<!--        </dependency> -->

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

application.properties

src/main/resources/application.propertiesにrabbitmqへの接続設定を記述する。

spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
spring.rabbitmq.username=xxxx
spring.rabbitmq.password=xxxx

送受信するPOJO

適当なクラスを作る。特に深い意味は無いけどlombokは使っていないので、コンストラクタ・getter/setterは省略。

import java.io.Serializable;
import java.util.List;

public class Parent implements Serializable {
    private static final long serialVersionUID = 1L;

    String id;
    List<Child> childs;
       
       //コンストラクタ・getter/setterは省略
}
import java.io.Serializable;

public class Child implements Serializable {
    private static final long serialVersionUID = 1L;

    String id;  
       //コンストラクタ・getter/setterは省略
}

send/receive

以下がエントリポイント。また、起動するとCommandLineRunnerで送信を行いrecievedMessageで受信を待ち受ける。以下のクラスを実行すると送受信が行われる。

コメントアウト箇所については後述。

import java.util.List;

import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;


@SpringBootApplication
public class App implements CommandLineRunner {
    public static void main(String[] args) {
        new SpringApplicationBuilder(App.class).web(WebApplicationType.NONE).run(args);
    }

    @Autowired
    AmqpTemplate amqpTemplate;
    
    @Override
    public void run(String... args) throws Exception {
        var message = new Parent("id12345", List.of(new Child("1"), new Child("2"), new Child("3")));
        amqpTemplate.convertAndSend("test-exchange", "", message);
    }
    
// @Bean
// public MessageConverter jsonMessageConverter(){
//     return new Jackson2JsonMessageConverter();
// }
    
    @RabbitListener(queues="test-queue")
    public void recievedMessage(Parent company) {
      System.out.println("company.id = " + company.id);
      company.childs.forEach(c -> System.out.println("child.id   = " + c.id));
    }
}

管理画面でメッセージを確認

RabbitMQの管理画面から見ると、以下のようにメッセージはバイナリで人間の眼では読めない形式になっている。

f:id:kagamihoge:20191114150050j:plain

JSONで送受信

次に、送受信するPOJOJSON形式に変更する。変更するには、上述のソースでコメントアウトの箇所を外す。

pom.xml

         <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-json</artifactId>
        </dependency>

App.java

import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;

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

上記変更を加えた後、実行してRabbitMQの管理画面から見ると、以下のようにメッセージがJSON形式になるのが確認できる。

f:id:kagamihoge:20191114150655j:plain

はまった点

Caused by: java.lang.ClassNotFoundException: com.fasterxml.jackson.databind.ObjectMapper

JSONで送受信する場合、pom.xmlspring-boot-starter-jsonの依存性を追加しないと、以下のような実行時エラーが発生する。

Caused by: java.lang.ClassNotFoundException: com.fasterxml.jackson.databind.ObjectMapper
    at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:602) ~[na:na]
    at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178) ~[na:na]

SimpleMessageConverter only supports String, byte[] and Serializable payloads

バイナリ形式で送受信する場合、POJOSerializableをimplementsする必要がある。JSONの場合はSerializableは要らない。

Caused by: java.lang.IllegalArgumentException: SimpleMessageConverter only supports String, byte[] and Serializable payloads, received: kagami.amqp.Parent
    at org.springframework.amqp.support.converter.SimpleMessageConverter.createMessage(SimpleMessageConverter.java:161) ~[spring-amqp-2.2.0.RELEASE.jar:2.2.0.RELEASE]

No serializer found for class kagami.amqp.Parent and no properties discovered to create BeanSerializer

POJOにgetter/setterが無い、あるいは、publicプロパティが無い場合に以下のような実行時エラーが発生する。

Caused by: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class kagami.amqp.Parent and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS)
    at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:77) ~[jackson-databind-2.10.0.jar:2.10.0]
    at com.fasterxml.jackson.databind.SerializerProvider.reportBadDefinition(SerializerProvider.java:1191) ~[jackson-databind-2.10.0.jar:2.10.0]