お仕事でSpring Integrationを使う機会があり、古いバージョンから新しいバージョンへアップデートを試みた際に、旧バージョンで動いていた処理が新バージョンで動かない(=そもそも処理が呼び出されない)という事象にぶつかってしまいました。複数のマイナーバージョンを跨ぐアップデートだったのである程度破壊的な変更が含めれていることは覚悟していましたが、想定どおり動いているケースもあったので最初は理由がよくわかりませんでした。
謎事象が起こった仕組みは?
よくわからない事象が発生したのは、TCP通信処理に任意の処理を割り込ませることができる仕組み(TcpConnectionInterceptor
)を使って処理を行っているところでした。
そもそもTCP通信処理ってどんな感じなの?
TcpConnectionInterceptor
の話をする前にめっちゃ簡単にSpring IntegrationのTCP通信処理がどんな感じでメッセージの送信と受信を行っているのかを紹介しておきます。実際には複数の仕組みが存在しますが、ここでは代表的なパターンを一つ紹介しておきます。
以下はNIOを利用した時の処理フローの概要イメージになります。
メッセージの送信
メッセージの送信を行う際は、TcpSendingMessageHandler
(TcpSender
を実装している)というコンポーネントを介してTcpConnection
のsend
メソッドを介してNIOのAPIを使ってデータを書き込みます。
メッセージの受信
メッセージの受信を行う際は、NIOのAPI経由で読み取ったデータをTcpListener
のonMessage
メソッドを介してTcpReceivingChannelAdapter
へ渡され、その後はTcpReceivingChannelAdapter
に紐付たチャネルへメッセージが連携されます。
TcpConnectionInterceptor
って何もの?
TcpConnectionInterceptor
は、ざっくりいうと・・・
-
送信処理であれば
TcpSender
からTcpConnection
のsend
メソッドを呼び出した際に、TcpConnection
本体のメソッドが呼び出される前に任意の処理を割り込みすることができる -
受信処理であれば
TcpConnection
からTcpListener
のonMessage
メソッドを呼び出した際に、TcpListener
本体のメソッドが呼び出される前に任意の処理を割り込みすることができる
仕組みです。
NOTE:
正確にいうと・・・
send
やonMessage
以外のメソッドにも割り込みが可能ですが、本投稿では意図的に割愛します。
どんな謎事象だった?
前提となる最低限の情報を共有したところで、今回発生した謎事象がどんなものだったかというと・・・・
- TCPサーバ(=リッスンポートを立ち上げてクライアントからつないでもらうモード)で扱う
TcpConnection
に複数のTcpConnectionInterceptor
を適用していると送信処理にて最初のインタセプタしか適用されない
というものでした。具体的には以下のような処理フローになっていました。
NOTE:
この事象はSpring Integrationへバグレポート済み(#8609)で、次回の計画リリースでリリースされる予定です。
NOTE:
ここでは詳細は割愛しますが、TCPクライアント(=他のTCPサーバへつなぎにいくモード)の場合は
TcpSender
で利用するTcpConnection
を決定する仕組みが異なるため本事象は発生しません。⇨ クライアントモード時は動いていたので・・・謎事象だと思ってしまいましたw
原因は?
通常はライブラリ利用者が気にする必要がない部分ですが、Spring Integrationはコネクション生成時に以下のような処理を行っています。
- 接続処理を行い
TcpConnection
本体(例:TcpNioConnection
)の生成 -
TcpConnectionInterceptor
を生成し、オリジナルのTcpConnection
または他のインタセプタをラップ - 最後(=大外)の
TcpConnectionInterceptor
を介してTcpSender
を登録 → メソッドチェインにより各インタセプタにTcpSender
が登録される - 各インタセプタに
TcpSender
を登録した後にTcpSender
で利用するTcpConnection
をaddNewConnection
メソッドを介して(=コールバックして)伝播する
本来であれば、TcpSender
(TcpSendingMessageHandler
)には「Interceptor3
」が伝播されるべきですが、「Interceptor1
」が伝播される仕組みになっていたために本事象が発生していました。
修正方法は?
TcpSender
で利用するTcpConnection
をaddNewConnection
メソッドを介して(=コールバックして)伝播する際に、チェイン先のaddNewConnection
メソッドにチェイン元のインスタンスを渡すように修正することで、TcpSender
(TcpSendingMessageHandler
)に「Interceptor3
」を伝播することができます。
本事象の影響を受けるバージョン
おそらく以下のバージョンでこの事象が発生します。
- Spring Integration 6.0.0 〜 6.0.5
- Spring Integration 5.5.9 〜 5.5.17
修正されるバージョン
2023/5/17リリース予定の以下のバージョンで本事象が解消される予定です。
- Spring Integration 6.1.0
- Spring Integration 6.0.6
- Spring Integration 5.5.18
バージョンアップせずに本事象を回避できないのか?
バージョンアップすることを推奨しますが・・・、修正版の正式バージョンリリース後であっても「何かしらの理由でSpring Integration自体のバージョンアップができない or したくない」が本事象は解消したい場合は、TcpSender
に「最後にインタセプタが適用されたコネクションを伝播するようなTcpConnectionInterceptor
」を適用することで本事象を回避することができます。
NOTE:
具体的に以下のGiHubリポジトリにサンプル実装があるので参考にしてみてください。
参考
- https://docs.spring.io/spring-integration/reference/html/ip.html#ip
- https://docs.spring.io/spring-integration/reference/html/ip.html#ip-interceptors
終わりに
久々にOSS活動して疲れましたw かなりサボっていたのでGWをきっかけにOSS活動も復活しよ〜