如何复用RestTemplate Bean并动态为其添加代理
复用RestTemplate Bean并动态切换代理的方案
完全可以复用同一个RestTemplate Bean,核心思路是将代理配置从RestTemplate的全局HttpClient中剥离,改为在单个请求级别动态设置,避免每次请求都创建新的RestTemplate实例。下面是两种可行的实现方式:
一、方式一:请求时通过RequestCallback动态设置代理
直接在调用RestTemplate的execute方法时,通过RequestCallback修改当前请求的代理配置,无需修改RestTemplate的全局配置。
1. 配置通用的RestTemplate Bean
这个Bean不带默认代理,使用基础的HttpClient:
@Bean public RestTemplate restTemplate() { // 创建基础HttpClient,不设置默认代理 CloseableHttpClient httpClient = HttpClients.custom() .setDefaultRequestConfig(RequestConfig.custom() .setConnectionRequestTimeout(3000) // 设置业务需要的超时时间 .build()) .build(); HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient); // 包装为BufferingClientHttpRequestFactory,匹配原有逻辑 ClientHttpRequestFactory bufferingFactory = new BufferingClientHttpRequestFactory(requestFactory); RestTemplate restTemplate = new RestTemplate(bufferingFactory); // 添加业务需要的消息转换器、拦截器等配置 return restTemplate; }
2. 封装动态代理的请求方法
写一个工具方法,需要代理时传入HttpProxy,不需要时传null:
@Autowired private RestTemplate restTemplate; public <T> T sendRequestWithProxy(String url, HttpMethod method, HttpProxy proxy, Class<T> responseType) { return restTemplate.execute(url, method, request -> { // 仅当传入代理时,修改当前请求的配置 if (proxy != null && request instanceof HttpComponentsClientHttpRequest) { HttpUriRequest httpUriRequest = ((HttpComponentsClientHttpRequest) request).getHttpUriRequest(); // 复制原有请求配置,仅添加代理 RequestConfig requestConfig = RequestConfig.copy(httpUriRequest.getConfig()) .setProxy(proxy) .build(); httpUriRequest.setConfig(requestConfig); } return null; }, response -> restTemplate.getMessageConverters().stream() .filter(converter -> converter.canRead(responseType, response.getHeaders().getContentType())) .findFirst() .map(converter -> (T) converter.read(responseType, response)) .orElse(null)); }
使用示例
// 无代理请求 String result1 = sendRequestWithProxy("https://example.com", HttpMethod.GET, null, String.class); // 带代理请求 HttpProxy proxy = new HttpHost("proxy.example.com", 8080); String result2 = sendRequestWithProxy("https://example.com", HttpMethod.GET, proxy, String.class);
二、方式二:基于ThreadLocal的全局动态代理切换
适合需要在多个请求中统一切换代理的场景(比如根据当前用户或请求参数自动切换),通过自定义ClientHttpRequestFactory读取ThreadLocal中的代理配置。
1. 定义代理上下文工具类
用ThreadLocal存储当前请求的代理,确保线程隔离:
public class ProxyContext { private static final ThreadLocal<HttpProxy> CURRENT_PROXY = new ThreadLocal<>(); // 设置当前请求的代理 public static void setCurrentProxy(HttpProxy proxy) { CURRENT_PROXY.set(proxy); } // 获取当前请求的代理 public static HttpProxy getCurrentProxy() { return CURRENT_PROXY.get(); } // 清除代理,避免内存泄漏 public static void clearCurrentProxy() { CURRENT_PROXY.remove(); } }
2. 自定义支持动态代理的RequestFactory
继承HttpComponentsClientHttpRequestFactory,在创建请求时读取ThreadLocal中的代理:
public class DynamicProxyHttpRequestFactory extends HttpComponentsClientHttpRequestFactory { @Override protected HttpUriRequest createHttpUriRequest(HttpMethod httpMethod, URI uri) { HttpUriRequest request = super.createHttpUriRequest(httpMethod, uri); HttpProxy proxy = ProxyContext.getCurrentProxy(); if (proxy != null) { // 复制原有配置并添加代理 RequestConfig requestConfig = RequestConfig.copy(request.getConfig()) .setProxy(proxy) .build(); request.setConfig(requestConfig); } return request; } }
3. 配置RestTemplate Bean
使用自定义的DynamicProxyHttpRequestFactory:
@Bean public RestTemplate restTemplate() { CloseableHttpClient httpClient = HttpClients.custom() .setDefaultRequestConfig(RequestConfig.custom() .setConnectionRequestTimeout(3000) .build()) .build(); DynamicProxyHttpRequestFactory requestFactory = new DynamicProxyHttpRequestFactory(); requestFactory.setHttpClient(httpClient); ClientHttpRequestFactory bufferingFactory = new BufferingClientHttpRequestFactory(requestFactory); RestTemplate restTemplate = new RestTemplate(bufferingFactory); // 添加业务需要的消息转换器等配置 return restTemplate; }
使用示例
@Autowired private RestTemplate restTemplate; public String requestWithDynamicProxy(boolean useProxy) { try { if (useProxy) { HttpProxy proxy = new HttpHost("proxy.example.com", 8080); ProxyContext.setCurrentProxy(proxy); } // 直接使用RestTemplate,会自动读取ThreadLocal中的代理配置 return restTemplate.getForObject("https://example.com", String.class); } finally { // 必须清除,避免线程复用导致的代理残留 ProxyContext.clearCurrentProxy(); } }
关键说明
- 原来的实现无法复用RestTemplate,是因为把代理绑定到了HttpClient的全局默认配置上,每个RestTemplate对应一个专属HttpClient。而上述方案都是在单个请求级别设置代理,全局HttpClient保持无代理的基础配置,因此RestTemplate可以安全复用。
- 测试场景下,只需注入同一个RestTemplate Bean,通过上述两种方式切换代理状态即可,无需重复创建实例。
内容的提问来源于stack exchange,提问作者CurvedHalo
相关产品推荐
相关产品推荐

