Spring Cloud RestTemplate @LoadBalanced 支持ip、域名、服务名 调用
RestTemplate设置
@LoadBalanced
@Bean
public RestTemplate restTemplate(@Autowired ClientHttpRequestFactory clientHttpRequestFactory){
return new RestTemplate(clientHttpRequestFactory);
}
如果使用时通过域名或者ip:port调用,例如
restTemplate.getForObject("https://fanyi.baidu.com/#en/zh/additional", String.class)
则会报错
java.lang.IllegalStateException: No instances available for fanyi.baidu.com
at org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient.execute(RibbonLoadBalancerClient.java:105) ~[spring-cloud-netflix-ribbon-2.1.0.RELEASE.jar:2.1.0.RELEASE]
at org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient.execute(RibbonLoadBalancerClient.java:93) ~[spring-cloud-netflix-ribbon-2.1.0.RELEASE.jar:2.1.0.RELEASE]
at org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor.intercept(LoadBalancerInterceptor.java:55) ~[spring-cloud-commons-2.1.0.RELEASE.jar:2.1.0.RELEASE]
官方针对这种情况给出的方案是创建两个RestTemplate,在不同的的情况下区分使用
@Configuration
public class MyConfiguration {
@LoadBalanced
@Bean
RestTemplate loadBalanced() {
return new RestTemplate();
}
@Primary
@Bean
RestTemplate restTemplate() {
return new RestTemplate();
}
}
public class MyClass {
@Autowired
private RestTemplate restTemplate;
@Autowired
@LoadBalanced
private RestTemplate loadBalanced;
public String doOtherStuff() {
return loadBalanced.getForObject("http://stores/stores", String.class);
}
public String doStuff() {
return restTemplate.getForObject("http://example.com", String.class);
}
}
我的方案
在“LoadBalancerInterceptor”(参见:LoadBalancerAutoConfiguration)之前添加自定的“MixLoadBalancerInterceptor”,拦截处理域名、ip方式调用
实现
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.web.client.RestTemplate;
import MixLoadBalancerInterceptor;
@Configuration
public class LoadBalancedRestTemplateConfig {
@Bean
public SmartInitializingSingleton mixLoadBalancedRestTemplateInitializer(
@Autowired List<RestTemplate> restTemplates,
@Autowired MixLoadBalancerInterceptor mixLoadBalancerInterceptor) {
return () -> {
for (RestTemplate restTemplate : restTemplates) {
List<ClientHttpRequestInterceptor> list = new ArrayList<>(restTemplate.getInterceptors());
list.add(mixLoadBalancerInterceptor);
restTemplate.setInterceptors(list);
}
};
}
@Bean
public MixLoadBalancerInterceptor mixLoadBalancerInterceptor(@Autowired ClientHttpRequestFactory clientHttpRequestFactory) {
return new MixLoadBalancerInterceptor(clientHttpRequestFactory);
}
}
import java.io.IOException;
import java.net.URI;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.PriorityOrdered;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpRequest;
import org.springframework.http.StreamingHttpOutputMessage;
import org.springframework.http.client.*;
import org.springframework.util.Assert;
import org.springframework.util.StreamUtils;
public class MixLoadBalancerInterceptor implements ClientHttpRequestInterceptor, PriorityOrdered {
@Value("${mix.loadbalancer.ip.regex:(((2[0-4]\\d|25[0-5]|[01]?\\d\\d?)\\.){3}(2[0-4]\\d|25[0-5]|[01]?\\d\\d?))}")
private String ipRegex;
@Value("${mix.loadbalancer.domain.regex:.*\\.(com|xyz|net|top|tech|org|gov|edu|pub|cn|biz|cc|tv|info|im)}")
private String domainRegex;
@Value("${mix.loadbalancer.additional.regex:}")
private String additionalRegex;
private ClientHttpRequestFactory httpRequestFactory;
public MixLoadBalancerInterceptor(ClientHttpRequestFactory httpRequestFactory) {
this.httpRequestFactory = httpRequestFactory;
}
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
final URI originalUri = request.getURI();
String serviceName = originalUri.getHost();
Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);
// 判断是否为ip、域名
if (serviceName.matches(String.format("^%s|%s|%s$", ipRegex, domainRegex, additionalRegex))) {
// 实际http请求,这段拷贝于:InterceptingClientHttpRequest
HttpMethod method = request.getMethod();
Assert.state(method != null, "No standard HTTP method");
ClientHttpRequest delegate = httpRequestFactory.createRequest(request.getURI(), method);
request.getHeaders().forEach((key, value) -> delegate.getHeaders().addAll(key, value));
if (body.length > 0) {
if (delegate instanceof StreamingHttpOutputMessage) {
StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage) delegate;
streamingOutputMessage.setBody(outputStream -> StreamUtils.copy(body, outputStream));
}
else {
StreamUtils.copy(body, delegate.getBody());
}
}
return delegate.execute();
}
else {
// 如果非ip、域名,继续走@LoadBalanced的原逻辑
return execution.execute(request, body);
}
}
@Override
public int getOrder() {
return 0;
}
}
导入Config
@Import({ ***.config.LoadBalancedRestTemplateConfig.class })
在使用上面方式发起http请求时,将会被正确处理
默认支持:ip、以及域名后缀:com、xyz、net、top、tech、org、gov、edu、pub、cn、biz、cc、tv、info、im
如果想支持其他的域名格式,可以在application.properties中增加配置项
mix.loadbalancer.additional.regex=.*\\.(int|mil)
版本信息
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- 版本为:2.1.0.RELEASE -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>