RestTemplate 增强
概述
Spring Cloud 中,RestTemplate 配合 @LoadBalanced 注解可以实现客户端负载均衡。相比 Feign 的声明式调用,RestTemplate 更灵活,适合第三方外部接口调用和非标准 HTTP 场景。
三套方案对比
| 方案 | 适用场景 | 风格 |
|---|---|---|
| Feign | 内部服务间调用 | 声明式(接口 + 注解) |
| RestTemplate | 外部三方 API + 简单内部调用 | 编程式(URL + 参数) |
| WebClient | 响应式场景 | 响应式(Flux/Mono) |
一、基础配置
1.1 自动配置
Spring Cloud 自动注册 @LoadBalanced RestTemplate Bean:
java
@Configuration
public class RestTemplateConfig {
@Bean
@LoadBalanced // 启用负载均衡
public RestTemplate loadBalancedRestTemplate() {
return new RestTemplate();
}
@Bean // 无需负载均衡(外部调用)
public RestTemplate plainRestTemplate() {
return new RestTemplate();
}
}1.2 使用
java
@Service
public class OrderService {
@Autowired
@LoadBalanced
private RestTemplate restTemplate; // 支持服务名调用
@Autowired
private RestTemplate plainRestTemplate; // 仅支持 IP + 端口
// 内部服务调用(负载均衡)
public User getUser(Long userId) {
return restTemplate.getForObject(
"http://user-service/api/users/{id}",
User.class, userId
);
}
// 外部 API 调用
public String callExternalApi(String url) {
return plainRestTemplate.getForObject(url, String.class);
}
}二、自定义配置
2.1 连接池 + 超时
java
@Configuration
public class RestTemplateConfig {
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate(clientHttpRequestFactory());
}
private ClientHttpRequestFactory clientHttpRequestFactory() {
// 使用 HttpComponents(Apache HttpClient)
HttpComponentsClientHttpRequestFactory factory =
new HttpComponentsClientHttpRequestFactory();
// 连接超时
factory.setConnectTimeout(5000);
// 读取超时
factory.setReadTimeout(10000);
// 连接获取超时(从连接池)
factory.setConnectionRequestTimeout(2000);
// HttpClient 连接池配置
HttpClient httpClient = HttpClientBuilder.create()
.setMaxConnTotal(200) // 最大连接数
.setMaxConnPerRoute(50) // 每个路由最大连接
.setConnectionTimeToLive(30, TimeUnit.SECONDS)
.evictIdleConnections(30, TimeUnit.SECONDS)
.build();
factory.setHttpClient(httpClient);
return factory;
}
}2.2 依赖
xml
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
</dependency>2.3 配置文件方式
yaml
spring:
cloud:
loadbalancer:
retry:
enabled: true # 启用重试
ribbon:
enabled: false # 禁用已废弃的 Ribbon三、拦截器链
3.1 请求拦截器
java
@Component
public class RestTemplateInterceptor implements ClientHttpRequestInterceptor {
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body,
ClientHttpRequestExecution execution)
throws IOException {
// 添加全局 Header
request.getHeaders().add("X-Source", "order-service");
request.getHeaders().add("Content-Type", "application/json");
// 透传 TraceId(链路追踪)
String traceId = MDC.get("traceId");
if (traceId != null) {
request.getHeaders().add("X-Trace-Id", traceId);
}
// 添加认证 Token
String token = SecurityContextHolder.getContext().getAuthentication() != null
? "Bearer " + getCurrentToken()
: null;
if (token != null) {
request.getHeaders().add("Authorization", token);
}
return execution.execute(request, body);
}
}3.2 注册拦截器
java
@Configuration
public class RestTemplateConfig {
@Autowired
private RestTemplateInterceptor interceptor;
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate(clientHttpRequestFactory());
// 添加拦截器(先添加的先执行)
restTemplate.getInterceptors().add(interceptor);
restTemplate.getInterceptors().add(new LoggingInterceptor());
return restTemplate;
}
}3.3 日志拦截器
java
@Component
public class LoggingInterceptor implements ClientHttpRequestInterceptor {
private static final Logger log = LoggerFactory.getLogger("HTTP_CLIENT");
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body,
ClientHttpRequestExecution execution)
throws IOException {
long start = System.currentTimeMillis();
ClientHttpResponse response = execution.execute(request, body);
long duration = System.currentTimeMillis() - start;
log.info("{} {} -> {} ({}ms)",
request.getMethod(),
request.getURI(),
response.getStatusCode(),
duration);
return response;
}
}四、重试机制
4.1 Spring Retry 配置
yaml
spring:
cloud:
loadbalancer:
retry:
enabled: true
max-retries-on-same-service-instance: 1 # 同实例重试次数
max-retries-on-next-service-instance: 1 # 切换实例重试次数
retryable-status-codes: 500,502,503 # 可重试的 HTTP 状态码4.2 自定义重试策略
java
@Configuration
public class RetryConfig {
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate(clientHttpRequestFactory());
restTemplate.setErrorHandler(new RetryErrorHandler());
return restTemplate;
}
// 自定义错误处理器
static class RetryErrorHandler extends DefaultResponseErrorHandler {
@Override
public boolean hasError(ClientHttpResponse response) throws IOException {
HttpStatus status = response.getStatusCode();
// 4xx 不重试(客户端错误),5xx 重试
return status.is5xxServerError();
}
}
}4.3 重试注意事项
java
// 幂等性保证:只有 GET / DELETE / PUT 等幂等方法才适合重试
// POST 提交订单等非幂等操作重试可能导致重复扣款
// 方案 1:在拦截器中判断 HTTP 方法
if ("GET".equals(request.getMethod()) || "DELETE".equals(request.getMethod())) {
return execution.execute(request, body);
}
// 方案 2:使用 @Retryable 注解手动控制
@Retryable(
retryFor = {TimeoutException.class},
maxAttempts = 3,
backoff = @Backoff(delay = 1000, multiplier = 2)
)
public Order getOrder(Long orderId) {
return restTemplate.getForObject(
"http://order-service/api/orders/{id}",
Order.class, orderId);
}五、混合调用实战
5.1 场景
一个电商订单服务需要调用多个目标:
| 目标 | 协议 | 方式 | 说明 |
|---|---|---|---|
| 用户服务 | HTTP | Feign | 内部微服务,接口固定 |
| 商品服务 | HTTP | Feign | 内部微服务,接口固定 |
| 微信支付 | HTTPS | RestTemplate | 外部三方,请求签名 |
| 物流查询 | HTTPS | WebClient | 响应式,高并发查询 |
| 短信发送 | HTTP | RestTemplate | 外部三方,简单调用 |
5.2 多 RestTemplate Bean
java
@Configuration
public class RestTemplateBeans {
// 用于内部服务调用(带负载均衡)
@Bean
@LoadBalanced
@Qualifier("internal")
public RestTemplate internalRestTemplate() {
return new RestTemplate(internalFactory());
}
// 用于外部三方 API(带重试 + 超时)
@Bean
@Qualifier("external")
public RestTemplate externalRestTemplate() {
HttpComponentsClientHttpRequestFactory factory =
new HttpComponentsClientHttpRequestFactory();
factory.setConnectTimeout(3000);
factory.setReadTimeout(5000);
RestTemplate rt = new RestTemplate(factory);
rt.getInterceptors().add(new ExternalApiInterceptor());
return rt;
}
// 用于支付回调(无负载均衡,短超时)
@Bean
@Qualifier("payment")
public RestTemplate paymentRestTemplate() {
SimpleClientHttpRequestFactory factory =
new SimpleClientHttpRequestFactory();
factory.setConnectTimeout(2000);
factory.setReadTimeout(3000);
return new RestTemplate(factory);
}
}5.3 使用示例
java
@Service
public class MixedCallService {
@Autowired
@Qualifier("internal")
private RestTemplate internalRestTemplate;
@Autowired
@Qualifier("external")
private RestTemplate externalRestTemplate;
@Autowired
private WebClient.Builder webClientBuilder;
@Autowired
private UserServiceClient userFeignClient;
// 场景:下单流程
@Transactional
public Order createOrder(CreateOrderRequest request) {
// 1. Feign 调用用户服务(内部微服务)
User user = userFeignClient.getUser(request.getUserId());
// 2. RestTemplate 调用商品服务(负载均衡)
Product product = internalRestTemplate.getForObject(
"http://product-service/api/products/{id}",
Product.class, request.getProductId());
// 3. RestTemplate 调用外部物流查询(无负载均衡)
String tracking = externalRestTemplate.getForObject(
"https://api.logistics.com/tracking/{orderNo}",
String.class, request.getOrderNo());
// 4. WebClient 批量查询物流状态(响应式)
Flux<LogisticsStatus> statuses = webClientBuilder.build()
.get()
.uri("http://product-service/api/products/batch")
.retrieve()
.bodyToFlux(LogisticsStatus.class);
// 5. 保存订单
return orderRepository.save(request.toOrder());
}
}六、Feign vs RestTemplate 选型
| 维度 | Feign | RestTemplate |
|---|---|---|
| 声明式 | 接口 + 注解 | 编程式 URL |
| 可读性 | 高 | 一般 |
| 灵活性 | 低(固定接口) | 高(动态 URL) |
| 调试 | 困难(代理) | 容易 |
| 日志 | 内置 | 需要拦截器 |
| 断路器 | 原生支持 | 需要额外封装 |
| 推荐场景 | 内部服务间调用 | 外部 API + 动态调用 |
七、总结
| 知识点 | 说明 |
|---|---|
@LoadBalanced | 启用客户端负载均衡 |
| 连接池 | HttpComponentsClientHttpRequestFactory + HttpClient |
| 拦截器 | ClientHttpRequestInterceptor 链 |
| 重试 | Spring Retry + LoadBalancer Retry |
| 多 Bean | @Qualifier 区分不同场景的 RestTemplate |
| 混合调用 | Feign + RestTemplate + WebClient 并存 |
参考链接: