SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

Spring AMQP×RabbitMQで始めるメッセージキューイング

Spring AMQPの実装と、AMQPを利用したアプリケーションの稼働

Spring AMQP×RabbitMQで始めるメッセージキューイング 第2回

ProducerとConsumerの実行

 では、これまでに用意したConsumerとProducerの両クラスを実行してください。Producer側では、5秒おきに送信しているメッセージ内容が標準出力に表示されます。

Producerの標準出力例
2013-01-02 11:14:37,527  INFO ation.AnnotationConfigApplicationContext: 510 - Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@6be4b53d: startup date [Wed Jan 02 11:14:37 JST 2013]; root of context hierarchy
2013-01-02 11:14:37,867  INFO ation.AnnotationConfigApplicationContext:1374 - Bean 'org.springframework.scheduling.annotation.SchedulingConfiguration' of type [class org.springframework.scheduling.annotation.SchedulingConfiguration$$EnhancerByCGLIB$$db59c7fc] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2013-01-02 11:14:37,938  INFO ctory.support.DefaultListableBeanFactory: 577 - Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@36927633: defining beans [org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,producerConfig,org.springframework.context.annotation.ConfigurationClassPostProcessor.importAwareProcessor,org.springframework.scheduling.annotation.SchedulingConfiguration,org.springframework.context.annotation.internalScheduledAnnotationProcessor,scheduledProducerTask,connectionFactory,rabbitTemplate]; root of factory hierarchy
Message sent at Wed Jan 02 11:14:38 JST 2013
Message sent at Wed Jan 02 11:14:43 JST 2013
Message sent at Wed Jan 02 11:14:48 JST 2013
Message sent at Wed Jan 02 11:14:53 JST 2013

 Consumer側で受け取ったメッセージが標準出力に出力されますので、Producer側と同じメッセージが出力されます。

Consumerの標準出力例
2013-01-02 11:17:11,247  INFO ation.AnnotationConfigApplicationContext: 510 - Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@428d9b22: startup date [Wed Jan 02 11:17:11 JST 2013]; root of context hierarchy
2013-01-02 11:17:11,556  INFO ctory.support.DefaultListableBeanFactory: 577 - Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@3f7de242: defining beans [org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,sampleListenerConfig,org.springframework.context.annotation.ConfigurationClassPostProcessor.importAwareProcessor,connectionFactory,listenerContainer]; root of factory hierarchy
2013-01-02 11:17:11,757  INFO ontext.support.DefaultLifecycleProcessor: 334 - Starting beans in phase 2147483647
Received: Message sent at Wed Jan 02 11:14:38 JST 2013
Received: Message sent at Wed Jan 02 11:14:43 JST 2013
Received: Message sent at Wed Jan 02 11:14:48 JST 2013

 そのまま起動し続けるとProducer側では5秒おきにメッセージ送信が、Consumer側ではメッセージを受け取るたびにhandleMessageに定義した処理内容が実行され続けることが分かります。今回のサンプルでは、Producerでメッセージ送信されるとほぼ同時にConsumer側のハンドラが実行されるため実感しにくいのですが、Consumer側では5秒おきに処理が実行されているわけではなく、あくまでもメッセージを受け取るたびに処理が実行されています。

メッセージコンバータを利用する

 SimpleMessageListenerContainerを利用した場合でも、前回紹介したメッセージコンバータが利用可能です。メッセージコンバータの指定は、MessageListenerAdapterの初期化時にハンドラクラスに加えて利用するコンバータクラスをインスタンス化して指定します。前回同様にシンプルなJavaオブジェクト(POJO)を用意し、このオブジェクトとメッセージを送受信時に変換します。まずは以下のようなオブジェクトを用意してください。

SimplePojo.java
public class SimplePojo {
    private int key;
    private String message;
    
    public int getKey() {
        return key;
    }
    public void setKey(int key) {
        this.key = key;
    }
    public String getMessage() {
        return message;
    }
    public void setMessage(String message) {
        this.message = message;
    }
}

 次に、リスナー側でメッセージコンバータとして前回同様に、JsonMessageConverterを利用するようにSampleListenerConfigを変更します。

SampleListenerConfig.java(変更前)
(省略)
    @Bean
    public SimpleMessageListenerContainer listenerContainer(){
        SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
        container.setConnectionFactory(connectionFactory());
        container.setQueueNames(queueName);
        container.setMessageListener(new MessageListenerAdapter(new HelloWorldHandler()));
        return container;
    }
(省略)
SampleListenerConfig.java(変更後)
(省略)
    @Bean
    public SimpleMessageListenerContainer listenerContainer(){
        SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
        container.setConnectionFactory(connectionFactory());
        container.setQueueNames(queueName);
        container.setMessageListener(new MessageListenerAdapter(new HelloWorldHandler(),new JsonMessageConverter()));
        return container;
    }
(省略)

 送信側のソースを作成したSimplePojoを利用する形に変更します。SimplePojoのkeyには、AtomicIntegerを利用したカウンターを使って送信ごとにインクリメントした値をセットするようにしています。

ScheduledProducerTask.java(変更前)
(省略)
    @Scheduled(fixedDelay=5000)
    public void sendMessage() {
        String message = "Message sent at " + new Date();
        rabbitTemplate.convertAndSend(message);
        System.out.println(message);
    }
(省略)
ScheduledProducerTask.java(変更後)
(省略)
    private final AtomicInteger counter = new AtomicInteger();
    
    @Scheduled(fixedDelay=5000)
    public void sendMessage() {
        String message = "Message sent at " + new Date();
        SimplePojo simplePojo = new SimplePojo();
        simplePojo.setKey(counter.incrementAndGet());
        simplePojo.setMessage(message);
        rabbitTemplate.convertAndSend(simplePojo);
        System.out.println(message);
    }
(省略)

 最後に受信側のハンドラクラスであるHelloWorldHandlerを変更します。

ScheduledProducerTask.java(変更前)
public class HelloWorldHandler {
    public void handleMessage(String text) {
        System.out.println("Received: " + text);
    }
}
HelloWorldHandler.java(変更後)
public class HelloWorldHandler {
    public void handleMessage(SimplePojo simplePojo) {
        System.out.println("Received: Key = " + simplePojo.getKey() + ", message = " + simplePojo.getMessage());
    }
}

 HelloWorldHandlerでは、handleMessageメソッドの引数にメッセージの受信時に変換するオブジェクトを与えています。こうすることで、Json形式で送信されたメッセージが指定されたオブジェクトに変換されます。

異なる言語のアプリケーションからメッセージを送信するには?

 JsonMessageConverterでは、メッセージ受信時の変換先となる型の特定を、メッセージにセットされたヘッダの値を元に行なっています。SpringAMQPがメッセージを送信する際にメッセージのプロパティに"__TypeId__"というヘッダでそのメッセージの変換に必要なクラス名をFQCNでセットしています。JsonMessageConverterのデフォルトのクラスマッパーでは、この値を読み取ることで変換先のクラスが特定されています。

 従って、異なる言語やSpringAMQPを利用していないアプリケーションからメッセージを送信する際は、メッセージの送信時に"__TypeId__"ヘッダに変換先クラスをセットしておく必要があります。

Rubyから送信する例(抜粋)
EM.run do
    (省略)
    exchange.publish({ :key => 1, :message => "message" }.to_json,
    :message_id => UUIDTools::UUID.random_create.to_s,
    :routing_key => "sample_queue",
    :headers => {
        :__TypeId__ => "org.sample.pojo.SimplePojo"
    },
    :content_type => "application/json"
    ) do
        amqpConnection.disconnect {EM.stop}
    end
end

 なお、クラスマッパーは独自に用意することも可能です。設定時にJsonMessageConverterにご自分で用意したクラスマッパーをセットするだけです。

クラスマッパーのセット
    @Bean
    public SimpleMessageListenerContainer listenerContainer(){
        SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
        container.setConnectionFactory(connectionFactory());
        container.setQueueNames(queueName);
        
        JsonMessageConverter jsonMessageConverter = new JsonMessageConverter();
        CustomClassMapper classMapper = new CustomClassMapper();
        jsonMessageConverter.setClassMapper(classMapper);
        
        container.setMessageListener(new MessageListenerAdapter(new HelloWorldHandler(),jsonMessageConverter));
        
        return container;
    }

次のページ
MirroedQueue利用時の設定

この記事は参考になりましたか?

Spring AMQP×RabbitMQで始めるメッセージキューイング連載記事一覧
この記事の著者

西谷 圭介(ニシタニ ケイスケ)

TIS株式会社所属。金融系基幹システムの開発等に従事したのち、サービス企画・開発を担当。IaaS開発を経て、現在はアプリ開発者のためのPaaS「eXcale」の開発責任者兼プログラマとして活動中。Tw...

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/6943 2013/01/18 14:00

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー