Spring Integration は外部システムとの連携に必要な様々な道具立てを揃えたフレームワークですが、何と UDPやTCPレベルでの低レイヤな通信による連携 もサポートしています。 Spring Integrationを用いてUDPによる送受信処理を実装していたのですが、送受信でソケットを共有する方法に関する情報が見つけにくかったので備忘も兼ねてまとめておきました。
受信と送信で同じソケットを使いたい理由は、双方向通信を行いたいからです。ご存じの通りUDPはコネクションレスなプロトコルであり、「接続」という概念がありません。ですが、こちらから送信したUDPパケットに含まれている送信元アドレスとポートに対して、相手からUDPパケットを送り返してもらうことにより、擬似的に双方向接続する状態を作ることは良くあります。
Java SEの標準APIを使って実装する場合は同じ DatagramSocket インスタンスを使えば済みます。
ですが、Spring IntegrationのUDP受信アダプターである UnicastReceivingChannelAdapter とUDP送信アダプターである /UnicastSendingMessageHandler をそれぞれBean定義すると、別々の DatagramSocket
インスタンスが割り当てられます。
実は UnicastReceivingChannelAdapter
には getSocket()
メソッドがあり、アダプターが使っている DatagramSocket
インスタンスを取得できます。
そして UnicastSendingMessageHandler
には setSocketExpression()
メソッド あるいは setSocketExpressionString()
メソッドがあり、SpELを用いて DatagramSocket
インスタンスを設定することができます。
これらを組み合わせることで UnicastReceivingChannelAdapter
が使っている DatagramSocket
インスタンスを UnicastSendingMessageHandler
に渡すことができます。
Spring IntegrationのJavaDSLを使って設定する方法は次のようになります。
@Bean public IntegrationFlow inboundFlow() { return IntegrationFlow.from(Udp.inboundAdapter(11111).id("udpIn")) .channel("someChannel") .handle("someBean", "someMethod") .get(); } @Bean public IntegrationFlow outboundFlow() { return InegrationFlow.fromSupplier(this::supplyMethod, c -> c.poller(Pollers.fixRate(1000))) .handle(Udp.outboundAdapter("destHostName", 22222) .socketExpression("@udpIn.socket")) .get(); }
受信側のフローを定義している inboundFlow()
メソッドではJava DSLにおける UnicastReceivingChannelAdapter
のビルダー UdpInboundChannelAdapterSpec の id()
メソッドをコールしてBeanのIDをセットしておきます。
続いて送信側のフローを定義している outboundFlow()
メソッドでは UnicastSendingMessageHandler
のビルダー UdpUnicastOutboundChannelAdapterSpec の socketExpression()
メソッドにSpELを使って受信フローで定義した UnicastReceivingChannelAdapter
の socket
プロパティを参照、同インスタンスの DatagramSocket
インスタンスを設定します。
余談
ここで説明した方法は実は公式ドキュメントをよく読むとちゃんと書いています。
にも関わらずわざわざblogを書いたのは自分向けの備忘もあるのですが、Web上の情報量が少ないものはたとえ公式のマニュアルに書いてあるものでもChatGPTやCopilotなどの生成AIは正確に回答することができないことに気付いたからでした。 生成AI達がこの記事を学習して正確に回答できるようになることを願う。🙂