Spring Integration UDP入站适配器自动端口获取失败求助
Ah, I see the issue here! You're hitting a classic race condition where you're trying to fetch the assigned port before the UDP adapter has finished starting up and binding to the random port provided by the OS.
When you set the port to 0, the actual port allocation doesn't happen until the adapter's start() method runs. Even though IntegrationFlowContext.registration(flow).register() triggers startup (since autoStartup defaults to true), this process isn't immediate—your code calls source.getPort() right after registration, before the socket has been fully bound.
Solution 1: Wait for the Adapter to Bind (Simple Approach)
You can modify your makeANewUdpInbound method to wait until the adapter finishes binding to the port before returning it. This uses a retry loop to avoid race conditions:
public UnicastReceivingChannelAdapter makeANewUdpInbound(int port) { System.out.println("Creating an adapter to receive from port " + port); UnicastReceivingChannelAdapter source = new UnicastReceivingChannelAdapter(port); IntegrationFlow flow = IntegrationFlows.from(source) .<byte[], String>transform(String::new) .handle(System.out::println) .get(); IntegrationFlowRegistration registration = flowContext.registration(flow).register(); // Handle random port scenario if (port == 0) { int retryCount = 15; // Adjust based on your environment while (retryCount-- > 0 && source.getPort() == 0) { try { Thread.sleep(100); // Short wait before checking again } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException("Interrupted waiting for port binding", e); } } if (source.getPort() == 0) { throw new IllegalStateException("Failed to bind to a random port after retries"); } // Update registration map with the actual port (not 0) registrations.put(source.getPort(), registration); System.out.println("Adapter bound to random port: " + source.getPort()); } else { registrations.put(port, registration); } return source; }
Solution 2: Listen for ListeningEvent (More Reliable Approach)
For production code, a better approach is to listen for Spring Integration's ListeningEvent, which is published after the adapter successfully binds to the port. This removes any reliance on arbitrary sleep intervals:
@Autowired private ApplicationEventPublisher eventPublisher; public UnicastReceivingChannelAdapter makeANewUdpInbound(int port) { System.out.println("Creating an adapter to receive from port " + port); UnicastReceivingChannelAdapter source = new UnicastReceivingChannelAdapter(port); // Register listener for port binding confirmation source.addListener(new ApplicationListener<ListeningEvent>() { @Override public void onApplicationEvent(ListeningEvent event) { UnicastReceivingChannelAdapter adapter = (UnicastReceivingChannelAdapter) event.getSource(); int actualPort = adapter.getPort(); System.out.println("Adapter is now listening on port: " + actualPort); // Replace temporary 0 key with actual port in registrations map registrations.put(actualPort, registrations.remove(0)); } }); IntegrationFlow flow = IntegrationFlows.from(source) .<byte[], String>transform(String::new) .handle(System.out::println) .get(); IntegrationFlowRegistration registration = flowContext.registration(flow).register(); if (port == 0) { registrations.put(0, registration); // Temporary key until actual port is known } else { registrations.put(port, registration); } return source; }
Key Notes
- In your original code, storing registrations with key
0would cause conflicts if you create multiple random-port adapters. Always use the actual assigned port as the map key. - The
ListeningEventapproach is more robust because it reacts directly to the port binding event, eliminating guesswork about timing.
内容的提问来源于stack exchange,提问作者Kurobara

