確認環境
- Java 8
- STS 3.9.1.RELEASE
- Spring Framework 4.3.13
- Spring Integration 4.3.12
どういう問題?
おおまかなTCP/IP通信の仕組み
Spring Integration
ではTCP/IP通信をサポートしていて、TcpIn/OutboundGateway
や、In/OutboundChannelAdapter
が通信の窓口として用意され、それぞれにConnectionFactory
に紐づけられています。通信が必要になった場合、窓口となるGateway
またはAdapter
がConnectionFactory
からTcpConnection
インスタンスを生成し通信します。
インターセプタとは?
通信する際に、ログ出力など、なんらかの処理を挟み込みたい場合は、ConnectionFactory
のInterceptorFactoryChain
プロパティにTcpConnectionInterceptorSupport
を継承したクラスのインスタンスを返すTcpConnectionInterceptorFactory
クラスを指定します。以下は実装例です。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.integration.ip.tcp.connection.TcpConnectionInterceptorSupport;
import org.springframework.messaging.Message;
public class SimpleInterceptor extends TcpConnectionInterceptorSupport {
private static final Logger logger = LoggerFactory.getLogger(SimpleInterceptor.class);
public SimpleInterceptor(ApplicationEventPublisher publisher) {
super(publisher);
}
@Override
public void send(Message<?> message) throws Exception {
// メッセージ送信前にログ出力処理を挟み込む
logger.debug("send message via interceptor");
super.send(message);
}
}
import org.springframework.context.support.ApplicationObjectSupport;
import org.springframework.integration.ip.tcp.connection.TcpConnectionInterceptorFactory;
import org.springframework.integration.ip.tcp.connection.TcpConnectionInterceptorSupport;
public class SimpleInterceptorFactory extends ApplicationObjectSupport implements TcpConnectionInterceptorFactory {
public SimpleInterceptorFactory() {
}
@Override
public TcpConnectionInterceptorSupport getInterceptor() {
return new SimpleInterceptor(getApplicationContext());
}
}
<int-ip:tcp-connection-factory id="client1"
type="client" host="localhost" port="${availableServerSocket1}"
single-use="true" interceptor-factory-chain="interceptorFactoryChain" />
<bean id="interceptorFactoryChain"
class="org.springframework.integration.ip.tcp.connection.TcpConnectionInterceptorFactoryChain">
<property name="interceptors">
<array>
<bean
class="com.neriudon.example.tcp.interceptor.SimpleInterceptorFactory" />
</array>
</property>
</bean>
何が問題なの?
ConnectionFactory
がTcpConnection
を生成しSocket
レベルでの接続が確立されるタイミングでTcpConnectionOpenEvent
を発行しますが、ConnectionFactory
にインターセプタを設定していると、connectionfactoryname
プロパティが初期値であるunknown
のままになります。
以下はインターセプタを設定した場合と設定していない場合のログ出力結果の比較です。
インターセプタを設定した場合
11:58:32.397 [main] DEBUG com.neriudon.example.tcp.listener.TcpConnectionEventsListener - ★OPEN★ TcpConnectionOpenEvent [source=SimpleInterceptor:null], [factory=unknown, connectionId=localhost:50001:58459:aa1f25b0-570e-4631-8477-33a19f1bb6ba] **OPENED**
インターセプタを設定していない場合
11:58:32.434 [main] DEBUG com.neriudon.example.tcp.listener.TcpConnectionEventsListener - ★OPEN★ TcpConnectionOpenEvent [source=TcpNetConnection:localhost:50002:58460:a92051dd-9003-4563-be59-3675dee3112d], [factory=client2, connectionId=localhost:50002:58460:a92051dd-9003-4563-be59-3675dee3112d] **OPENED**
この問題が発生する理由と改善策
TcpConnectionInterceptorSupport
クラスにConnectionFactoryName
を設定する処理がないのが原因です。が、ConnectionFactory
の情報は実際に通信をするTcpConnection
が持っているので、ConnectionFactoryName
を返す時にTcpConnectionInterceptorSupport
がTcpConnection
のConnectionFactoryName
を返すようにすれば。詳しくは下記のPRのリンクを参照してください。
ちなみに他のTcpConnectionEvent
は元々TcpConnectionInterceptorSupport
がTcpConnection
の処理を呼び出すよう設計されているので問題ありません。
この問題による影響は?
ほとんど無いです。
TcpConnectionInterceptorSupport
がmasterに取り込まれたのが2016年3月で、それからバグ報告がされなかったのと、TcpConnectionOpenEvent
自体がSpring Integration
のドキュメントでも浅く書かれているので、この問題がプロジェクトに深刻な影響を与えてはいないでしょう。
いつ修正されるの?
リリースされる日付は分かりませんが、JIRAを見る限り、次の5.0.1, 4.3.14で修正されます。2018年1月15日時点でPRが出されているので、近々マージされるでしょう。
この記事を投稿するに至った経緯
この件についてstackoverflowで質問したのが私だからです。
Spring Integration
プロジェクトでは、使い方や仕様に関する質問はspring-integration
タグを付けてstackoverflowで質問するルールになっています。私はバグか仕様なのか判断がつかなかったので、サンプルアプリを作成したうえで、発生する条件やログを提示したところ、中の人からバグですと回答がありました。
しかし、もたもたしてるうちに中の人がJIRAに起票してしまったので、初のOSSへのバグ報告できなくて残念。
1/30 追記:
今回の修正を取り込んだ4.3.14.RELEASEおよび5.0.1.RELEASEがリリースされました。