2013年12月23日月曜日

Spring の JMS サンプルを試す

この記事は Spring Framework Advent Calendar 2013 の 12/23 の記事です。 

このエントリでは、Spring Guide で紹介されている JMS サンプルを試してみたいと思います。
# なんども掲載日を変更してしまい、すみません。
# 本当は WildFly/HornetQ で何かしら動かしてみたかったのですが、次回リベンジします。。

Spring では Guides として、用途別にたくさんのサンプルを載せています。

http://spring.io/guides

その中で JMS を利用したメッセージングのサンプルがあったので、試してみました。

http://spring.io/guides/gs/messaging-jms/

サンプルを clone し、内容を確認してみます。
$ git clone https://github.com/spring-guides/gs-messaging-jms.git
$ cd gs-messaging-jms
$ tree --dirsfirst
.
# 完成形
├── complete
│   ├── gradle
│   │   └── wrapper
│   │       ├── gradle-wrapper.jar
│   │       └── gradle-wrapper.properties
│   ├── src
│   │   └── main
│   │       └── java
│   │           └── hello
│   │               ├── Application.java
│   │               └── Receiver.java
│   ├── build.gradle
│   ├── gradlew
│   ├── gradlew.bat
│   └── pom.xml
# 最初の状態
├── initial
│   ├── gradle
│   │   └── wrapper
│   │       ├── gradle-wrapper.jar
│   │       └── gradle-wrapper.properties
│   ├── src
│   │   └── main
│   │       └── java
│   │           └── hello
│   ├── build.gradle
│   ├── gradlew
│   ├── gradlew.bat
│   └── pom.xml
# その他
├── test
│   └── run.sh
├── LICENSE.code.txt
├── LICENSE.writing.txt
├── README.adoc
├── SIDEBAR.ftl.md
└── SIDEBAR.md

complete に完成済みの答えが格納されています。initial はある程度の構成ができてここからソースや少々の設定を加えて complete に近づけていく、という形になっています。
今回作成する必要があるのは、
  • Application.java
  • Receiver.java
ですね。あとは Maven を利用するならば pom.xml を、Gradle であれば build.gradle を一部追記しておしまいです。では、ガイド に沿って、initial 以下を編集作成していきたいと思います。

Receiver.java

hello パッケージに Receiver.java を作成します。
package hello;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.util.FileSystemUtils;
import java.io.File;
public class Receiver {
/**
* Get a copy of the application context
*/
@Autowired
ConfigurableApplicationContext context;
/**
* When you receive a message, print it out, then shut down the application.
* Finally, clean up any ActiveMQ server stuff.
* @param message
*/
public void receiveMessage(String message) {
System.out.println("Received <" + message + ">");
context.close();
FileSystemUtils.deleteRecursively(new File("activemq-data"));
}
}
view raw Receiver.java hosted with ❤ by GitHub
MDB ? と見まごうばかりですが、これは Message Driven Pojo(MDP) であり、javax.jms.MessageListener を実装する必要はありません。onMessage ではなく、任意の名前のメソッドを作成し、メッセージコンシューマの実装を記述していきます。
最後の FileSystemUtils.deleteRecursively(new File("activemq-data")); で組込 ActiveMQ のデータを削除しています。

Application.java

hello パッケージに Application.java を作成します。

package hello;
import javax.jms.ConnectionFactory;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.Session;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.core.MessageCreator;
import org.springframework.jms.listener.SimpleMessageListenerContainer;
import org.springframework.jms.listener.adapter.MessageListenerAdapter;
import org.springframework.util.FileSystemUtils;
import java.io.File;
@Configuration
@EnableAutoConfiguration
public class Application {
static String mailboxDestination = "mailbox-destination";
@Bean
Receiver receiver() {
return new Receiver();
}
@Bean
MessageListenerAdapter adapter(Receiver receiver) {
return new MessageListenerAdapter(receiver) {
{
setDefaultListenerMethod("receiveMessage");
}
};
}
@Bean
SimpleMessageListenerContainer container(final MessageListenerAdapter messageListener,
final ConnectionFactory connectionFactory) {
return new SimpleMessageListenerContainer() {
{
setMessageListener(messageListener);
setConnectionFactory(connectionFactory);
setDestinationName(mailboxDestination);
setPubSubDomain(true);
}
};
}
public static void main(String[] args) {
// Clean out any ActiveMQ data from a previous run
FileSystemUtils.deleteRecursively(new File("activemq-data"));
// Launch the application
ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);
// Send a message
MessageCreator messageCreator = new MessageCreator() {
@Override
public Message createMessage(Session session) throws JMSException {
return session.createTextMessage("ping!");
}
};
JmsTemplate jmsTemplate = context.getBean(JmsTemplate.class);
System.out.println("Sending a new message.");
jmsTemplate.send(mailboxDestination, messageCreator);
}
}
まず目をひくのが、Class 宣言のところにある @Configuration と @EnableAutoConfiguration ですね。このプロジェクトは、Spring Boot というスタンドアロンなアプリケーションを迅速につくる仕組みを利用しており、この 2 つの設定のおかげで、だいたい Spring が設定をよきにはからってくれるというもののようです。確かに、プロジェクト中に applicationContext.xml などは見当たりません。すごい。

Spring Boot - AutoConfigure

とはいえ、自分で設定するところも必要です。

L27 - 30 では先ほど作成した Receiver を Bean 定義しています。Java クラス中に定義できてしまうんですね。
L32 - 39 では、メッセージ受信時に実行されるメソッドが定義するために、Receiver#receiveMessage をリスナ用メソッドに指定しています。Receiver が MDP でいられるのはこういった設定ができるからなんですねえ。
L41 - 52 では、SimpleMessageListenerContainer を返す container メソッドを定義しています。ここで MDP の設定はひと通り定義しています。

main() メソッドが、送信クライアントになっています。JmsTemplate を使っているので、かなりコードが短いですね。
# 送信に関しては、JMS2.0 もかなりシンプルに使える API ですので、EE 7 が待ち遠しいですね。

アプリケーションのビルド・実行

initial にある pom.xml/build.gradle に plugin を追加して、実行可能な jar を作成できるようにします。pom.xml の場合は spring-boot-maven-plugin プラグインを追加し、build.gradle の場合は dependencies と apply plugin を追加します。

Maven の場合


<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework</groupId>
<artifactId>gs-messaging-jms</artifactId>
<version>0.1.0</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>0.5.0.M6</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jms</artifactId>
</dependency>
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-core</artifactId>
</dependency>
<dependency>
<groupId>org.apache.geronimo.specs</groupId>
<artifactId>geronimo-jms_1.1_spec</artifactId>
<version>1.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-snapshots</id>
<url>http://repo.spring.io/libs-snapshot</url>
<snapshots><enabled>true</enabled></snapshots>
</repository>
<repository>
<id>spring-releases</id>
<url>http://repo.spring.io/release</url>
<snapshots><enabled>true</enabled></snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-snapshots</id>
<url>http://repo.spring.io/libs-snapshot</url>
<snapshots><enabled>true</enabled></snapshots>
</pluginRepository>
</pluginRepositories>
</project>
view raw pom.xml hosted with ❤ by GitHub

Gradle の場合


buildscript {
repositories {
maven { url "http://repo.spring.io/libs-snapshot" }
mavenLocal()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:0.5.0.M6")
}
}
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'idea'
apply plugin: 'spring-boot'
jar {
baseName = 'gs-messaging-jms'
version = '0.1.0'
}
repositories {
mavenCentral()
maven { url "http://repo.spring.io/libs-snapshot" }
}
dependencies {
compile("org.springframework.boot:spring-boot-starter:0.5.0.M6")
compile("org.springframework:spring-jms:4.0.0.RC1")
compile("org.apache.activemq:activemq-core:5.4.0")
compile("org.apache.geronimo.specs:geronimo-jms_1.1_spec:1.1")
testCompile("junit:junit:4.11")
}
task wrapper(type: Wrapper) {
gradleVersion = '1.8'
}
view raw build.gradle hosted with ❤ by GitHub
こう並べてみると Gradle は記述がさっぱりしていて、ぐらついてしまいますねえ。。

これでビルドできるようになったので、Maven または Gradle を使ってビルドします。

Maven の場合

$ mvn clean package

Gradle の場合

$ ./gradlew build

ビルドが完了したら実行します。

Maven の場合

$ java -jar target/gs-messaging-jms-0.1.0.jar

Gradle の場合

$ java -jar build/libs/gs-messaging-jms-0.1.0.jar

実行結果

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::             (v0.5.0.M6)

[...]
Sending a new message.
Received <ping>
[...]

Spring + JMS いい感じ

Spring の JMS クライアントはかなりいい感じということがわかりました。純粋に jms クライアントを書くよりシンプルに記述できますね。
また、MDP を使うことで EJB なしで非同期受信ができるのは特筆すべき点と思います。

今回の JMS プロバイダは組込みの ActiveMQ でしたが、今後は WildFly の JMS プロバイダである HornetQ を利用するなどもしてみようかと思います。

0 件のコメント:

コメントを投稿