为Spring Integration动态URL的Http Outbound Gateway配置动态超时
Question Context
I have an HTTP outbound gateway using dynamic URLs configured with fixed connection and read timeouts, like this:
<int-http:outbound-gateway request-channel="request" reply-channel="response" url-expression="headers.serviceUrl" http-method="POST" expected-response-type="java.lang.String" charset="UTF-8" request-factory="httpOutboundRequestFactoryBean" message-converters="messageConverterList" header-mapper="headerMapperBean"/> <bean id="httpOutboundRequestFactoryBean" class="org.springframework.http.client.HttpComponentsClientHttpRequestFactory"> <property name="readTimeout" value="5000"/> <property name="connectTimeout" value="1000"/> </bean>
Is there a way to configure specific timeouts based on the URL defined in headers.serviceUrl? I've searched and found related questions, with one approach being to use a RestTemplate directly (creating a new instance each time with a request factory configured with URL-specific timeouts). But maybe Spring Integration has a built-in way to handle this? Thanks for the help!
Solution
While Spring Integration doesn't have a direct out-of-the-box feature for per-URL dynamic timeouts with the HTTP outbound gateway, you can implement this cleanly by combining a ThreadLocal to pass dynamic timeout values with a custom extension of HttpComponentsClientHttpRequestFactory. Here's how to do it step by step:
1. Create a ThreadLocal Holder for Dynamic Timeouts
First, make a class to hold a thread-local variable that stores the dynamic timeout value. We'll set this value in a service activator before the HTTP outbound gateway (you can replace the random timeout logic with your own URL-to-timeout mapping):
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.messaging.Message; import java.util.Random; public class ThreadLocalTimeoutHolder { public static ThreadLocal<Integer> serviceTimeout = new ThreadLocal<>(); private static final Logger LOGGER = LoggerFactory.getLogger(ThreadLocalTimeoutHolder.class); public Message<?> setTimeout(final Message<?> message){ // Replace this with logic to map headers.serviceUrl to specific timeouts final int timeout = (new Random().nextInt(20 - 1 + 1) + 1)*1000; serviceTimeout.set(timeout); LOGGER.info("Set dynamic service timeout (thread-local): {}", timeout); return message; } }
Add this service activator to your flow to inject the dynamic timeout before the gateway:
<int:service-activator input-channel="preRequestChannel" output-channel="request" ref="threadLocalTimeoutHolder" method="setTimeout"/> <bean id="threadLocalTimeoutHolder" class="sample.ThreadLocalTimeoutHolder"/>
2. Extend HttpComponentsClientHttpRequestFactory
Next, extend the default request factory to override the mergeRequestConfig method. This is where we'll replace the default socket timeout (read timeout) with the value from our thread-local variable:
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.apache.http.client.config.RequestConfig; public class DynamicTimeoutRequestFactory extends HttpComponentsClientHttpRequestFactory { private RequestConfig requestConfig; private static final Logger LOGGER = LoggerFactory.getLogger(DynamicTimeoutRequestFactory.class); @Override protected RequestConfig createRequestConfig(final Object client) { this.requestConfig = super.createRequestConfig(client); return this.requestConfig; } @Override protected RequestConfig mergeRequestConfig(final RequestConfig clientConfig) { if (this.requestConfig == null) { return clientConfig; } final RequestConfig.Builder builder = RequestConfig.copy(clientConfig); // Preserve configured connect timeout final int connectTimeout = this.requestConfig.getConnectTimeout(); if (connectTimeout >= 0) { builder.setConnectTimeout(connectTimeout); } // Preserve configured connection request timeout final int connectionRequestTimeout = this.requestConfig.getConnectionRequestTimeout(); if (connectionRequestTimeout >= 0) { builder.setConnectionRequestTimeout(connectionRequestTimeout); } // Apply dynamic timeout from ThreadLocal final int socketTimeout = ThreadLocalTimeoutHolder.serviceTimeout.get(); LOGGER.info("Applying dynamic service timeout: {}", socketTimeout); if (socketTimeout >= 0) { builder.setSocketTimeout(socketTimeout); } // Clear ThreadLocal to avoid memory leaks in thread pools ThreadLocalTimeoutHolder.serviceTimeout.remove(); return builder.build(); } }
3. Update Spring Configuration
Finally, update your HTTP outbound gateway to use the custom request factory:
<int-http:outbound-gateway request-channel="request" reply-channel="response" url-expression="headers.serviceUrl" http-method="POST" expected-response-type="java.lang.String" charset="UTF-8" request-factory="dynamicTimeoutRequestFactory" message-converters="messageConverterList" header-mapper="headerMapperBean"/> <bean id="dynamicTimeoutRequestFactory" class="sample.DynamicTimeoutRequestFactory"> <property name="readTimeout" value="5000"/> <!-- Fallback timeout if no dynamic value is set --> <property name="connectTimeout" value="1000"/> </bean>
Key Notes
- Always clear the
ThreadLocalvariable after use to prevent memory leaks in thread pool environments. - Adjust the
setTimeoutmethod to map specific URLs (frommessage.getHeaders().get("serviceUrl")) to your predefined timeout values. - The configured
readTimeoutacts as a fallback if no dynamic timeout is provided for a URL.
内容的提问来源于stack exchange,提问作者gllambi

