Spring Cloud项目中使用RestTemplate调用接口,
如果使用时通过域名或者 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);
}
}
但是这样还是有点麻烦,比如正式环境由于部署了多份,需要使用服务名称调用,但是在测试环境或者开发环境,只是使用ip+端口调用。
可以使用拦截器。
在 “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;
}
}
启动项目是报错,LoadBalancedRestTemplateConfig required a bean of type ‘org.springframework.http.client.ClientHttpRequestFactory’ that could not be found.
解决方式如下:
@Configuration
public class RestTemplateConfig {
@Bean
public ClientHttpRequestFactory clientHttpRequestFactory() {
return new HttpComponentsClientHttpRequestFactory();
}
}
经过测试,支持服务名称、域名及IP调用。