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调用。