Spring Integration 勉強の備忘録 ~Spring Integration Sampleの理解 2. JMS Gateway~


## 目的
Spring Integrationを学ぶために、Spring Projectが提供しているサンプルを理解して、実装し直す + アレンジしてみた。本記事は、その備忘録として。なので色々間違いがあるとは思いますが、その点はご指摘いただければ幸いです。
サンプルは以下のGitに登録してあるもの。
[spring-integration-sample](https://github.com/spring-projects/spring-integration-samples) 
  
今回はbasicのjmsの内の1つである_**Gateway**_について。
ちなみにSpring Integrationのバージョンは```4.3.10.RELEASE```である。


## 概要
JMSでは3つの機能が実装されており、Mainクラスを実行すると対話形式で1~3のどのモードを試すかを決定するようになっている。今回は1のGatewayについてのみ実装を行った。
1. Gateway
2. Channel Adapter
3. Aggrigation

まだ全然わからないことだらけで、一辺に実装しても理解しにくいので同様の形式で実装するのではなく、個々に実装して理解していくこととする。

## 実装
### Gateway
#### 全体像
全体のフローはたぶん以下のような感じ。（間違っていたらどなたかご指摘ください）
```標準入力(stdin) -> stdinToJmsoutChannel -> RequestQueue -> ServiceActivator ->  RequestChannel -> ReplyQueue -> ReplyChannel -> 標準出力(stdout)```
  
  
コンソールから入力した値を、```outbound-gateway```が```stdin-channel-adapter```を介してqueue(```RequestQueue```)に詰める。次に、```inbound-gateway```が```RequestQueue```から値を取得して```ServiceActivator```で処理したものを```RequestChannel```に流す。それをまた、```outbound-gateway```が今度は```ReplyQueue```から取得して、```ReplyChannel```に中継して、最終的に標準出力を行う。
  
[Reference Manual](https://docs.spring.io/spring-integration/docs/4.3.18.BUILD-SNAPSHOT/reference/html/jms.html#jms-outbound-gateway)の_outbound-gateway_の説明だと、```ReplyQueue```(```reply-destination```属性)は指定しないこともでき、指定しないと```TemporaryQueue```が使われるとのこと。本サンプルでも未指定のため、```TemporaryQueue```から値を```ReplyChannel```に流していると思われれる。

#### bean定義ファイル
本サンプルは_common.xml_, _outboundGateway.xml_, _inboundGateway.xml_の3構成からなっている。  
私の実装したものは基本サンプルプロジェクトの使い回しであるが、理解のために不要そうな記述は全部抜いてある。

* common.xml

ここではaggregationとかchannelAdapterとか他の機能でも必要な設定を共通設定として定義してたが、今回はまずgatewayに必要なもの設定のみ残す形とした。

Gatewayのアプリに必要であるのは以下の設定。1の```connectionFactory```は内部的に```JmsOutboundGateway```クラスがコネクションを作成するために必要。2は```stdin-channel-adapter```が標準入力をポーリングするために必要っぽい。
そして、```requestQueue```は_outboundGateway_も_inboundGateway_でも使っているため定義が必要である。ここでは実装クラスとして、OSSの```ActiveMQ```のQueueを指定している。
  
他の設定は今回は不要そうだったので削除した。

```xml
    <!-- 1 -->
	<bean id="connectionFactory" class="org.springframework.jms.connection.CachingConnectionFactory">
		<property name="targetConnectionFactory">
			<bean class="org.apache.activemq.ActiveMQConnectionFactory">
				<property name="brokerURL" value="vm://localhost"/>
			</bean>
		</property>
		<property name="sessionCacheSize" value="10"/>
	</bean>

    <!-- 2 -->
	<bean id="requestQueue" class="org.apache.activemq.command.ActiveMQQueue">
		<constructor-arg value="queue.demo"/>
	</bean>

    <!-- 3 -->
	<integration:poller id="poller" default="true" fixed-delay="1000"/>
```

* outboundGateway.xml

ここも必要最小限の設定だけ残している。test用のprofile情報の設定等もあったが、まずは不要なので消した。
ここでは、```JmsOutboundGateway```クラスが、その名の通り処理フローを色々と中継する役割のため、中継するために必要な設定を記述している。
1,3がコンソールへの入力と出力について定義している箇所。2は、```request-channel```属性から取得した入力情報を、```requestQueue(MessageChannel)```の```send```メソッドに渡している。その戻り値は、今回は省略している```TemporaryQueue```に入るので、それを内部的に```reply-channel```属性のオブジェクトへと渡すという設定になっている模様。


```xml
     <!-- 1 -->
	<stream:stdin-channel-adapter id="stdin" channel="stdinToJmsoutChannel"/>
	<channel id="stdinToJmsoutChannel"/>

    <!-- 2 -->
	<jms:outbound-gateway request-channel="stdinToJmsoutChannel"
						  request-destination="requestQueue"
						  reply-channel="jmsReplyChannel"/>
						  
    <!-- 3 -->
	<channel id="jmsReplyChannel" />	
	<stream:stdout-channel-adapter channel="jmsReplyChannel" append-newline="true"/>

```

* inboundGateway.xml

最後はinboundの設定になるが、元々```ServiceActivator```の設定があったが、[Hello World](https://qiita.com/E-Kane/items/01eff7761009e1196d43)の記事でも実装したので、アノテーションで実装することにした。アノテーションにすることでxmlがスッキリしてやりたいことが分かり易くなった。
（_Spring Integration_はxmlが複雑になりがちなので、JavaConfigとかアノテーション主体で実装した方がいいのではと一瞬思った。が、アプリの規模が大きくなると、突如ソースコードが追いにくくなるようにも思う。webシステムよりそこら辺のさじ加減が難しそう。）

本題に戻ると、1はコンポーネントスキャンをしている。これにて、```ServiceActivator```を実装しているクラスをbean定義不要となる。次に2は```inbound-gateway```タグで内部的な処理フローを定義している。outboundとは逆に、```request-destination```属性から値を取得し、```request-channel```属性に値を伝搬させていく。

```xml
    <!-- 1 -->
	<context:component-scan base-package="org.ek.sample.jms.gateway"/>

    <!-- 2 -->
	<jms:inbound-gateway id="jmsin"
	                     request-destination="requestQueue"
	                     request-channel="requestChannel"/>

	<channel id="requestChannel"/>
```

#### ServiceActivator

既述の通り、```ServiceActivator```はアノテーションを使った。そのために(1) ```@EnableIntegration```アノテーションと、(2) ```@MessageEndpoint``` アノテーションを使っている。
(1)を使わない状態で```@ServiceActivator```を使うと、Dispatch先が見つからないという類のエラーが出る。Endpoint系のオブジェクトでアノテーション使うには一緒に付与する必要があるのかなーという簡単な認識だけしておいた。後々はもう少し中身を見たいが、今のところは我慢して先に進むことにする。

(2)は単にコンポーネントスキャン対象とするためにアノテーションである。webで言うところの```@Controller```とか```@Service```と一緒である。

(3)はまさにbean定義を置き換えたところである。

```java
@EnableIntegration // (1)
@MessageEndpoint // (2)
public class GatewayEndpoint {

    // (3)
	@ServiceActivator(inputChannel="requestChannel")
	public String upperCase(String input) {
		return "JMS response: " + input.toUpperCase();
	}
}
```

#### Mainクラス
_Main_クラスのコードは以下の通り。  
xmlを配列で定義して、それをApplicationContextのインスタンスとして読み込むだけ。
これだけで、コンソールとアプリとの対話を実現できる。

* Main

```java
	private final static String[] configFilesGatewayDemo = {
			"/META-INF/spring/integration/common/common.xml",
			"/META-INF/spring/integration/gateway/inboundGateway.xml",
			"/META-INF/spring/integration/gateway/outboundGateway.xml"
		};
	
	@SuppressWarnings("resource")
	public static void main(String[] args) {
		new ClassPathXmlApplicationContext(configFilesGatewayDemo,GatewayMain.class);
	}

```

### 最後に
今回はinbound-gatewayとかoutbound-gateway等を扱ったが、こいつらはinとoutのやり取りを後ろ側でよしなにやってくれるので結構使い勝手良さそうと思った。あとは、個人的に感覚的にxmlが読めないのでそこの可読性をどう上げるかを考えないと、自分で実装してても後で忘れそうという印象を持った。

コードはコチラ。[Jmsサンプルをいじったもの](https://github.com/E-Kanegae/SpringIntegrationStudy/tree/master/jmsOrigin)