@Author:zxw
@Email:502513206@qq.com
目录
- Feign源码分析(一) - 初探Feign
- Feign源码分析(二) - builder构建
- Feign源码分析(三) - Client调用
-
1.前言
在上一篇文章中已经分析了Feign有哪些常见的扩展点供我们使用,其中比较关键的几个点有如下
对Client的封装,整合ribbon和hytrix
-
2.扩展
2.1 Client
通过之前的分析了解到Feign默认提供的Client访问网络请求是使用原生java的网络请求。对于原生的HttpConnection听说性能上做的没有第三方工具包好,所以这边feign也提供了两种第三方的Client分别为
OKHTTP
和apache的HTTPCLIENT
,spring则是提供了LoadBalancerFeignClient
和FeignBlockingLoadBalancerClient
。我们先看下Spring提供的Feign配置@FeignClient(name = "order", url = "127.0.0.1:8082")
public interface OrderFeignClient {}
这里有个注意的点就是,如果配置了url,那么spring就会使用
OKHTTP
和HTTPCLIENT
其中一种,就不会使用ribbon了,所以如果想使用spring提供的Client,那么只需要name属性即可。
spring为Client创建提供了一个工厂类FeignClientFactoryBean
,在该类的getObject()
方法会构建我们的builder对象,最后调用target()生成接口的实例,对于client使用的判断如下// 判断url是否存在
if (!StringUtils.hasText(this.url)) {
if (!this.name.startsWith("http")) {
this.url = "http://" + this.name;
}
else {
this.url = this.name;
}
this.url += cleanPath();
// 如果不存在则使用loadBalance客户端
return (T) loadBalance(builder, context,
new HardCodedTarget<>(this.type, this.name, this.url));
}
if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
this.url = "http://" + this.url;
}
String url = this.url + cleanPath();
Client client = getOptional(context, Client.class);
if (client != null) {
if (client instanceof LoadBalancerFeignClient) {
// not load balancing because we have a url,
// but ribbon is on the classpath, so unwrap
client = ((LoadBalancerFeignClient) client).getDelegate();
}
if (client instanceof FeignBlockingLoadBalancerClient) {
// not load balancing because we have a url,
// but Spring Cloud LoadBalancer is on the classpath, so unwrap
client = ((FeignBlockingLoadBalancerClient) client).getDelegate();
}
builder.client(client);
}
2.1.1 ApacheHttpClient
对于调用,无非就是将内部的请求客户端换成httpClient那一套。不过spring默认情况下使用的是
ApacheHttpClient
来进行调用。可以看到默认的配置值为true,然后注入Client对象为ApacheHttpClient
```java @Configuration(proxyBeanMethods = false) @ConditionalOnClass(ApacheHttpClient.class) @ConditionalOnProperty(value = “feign.httpclient.enabled”, matchIfMissing = true) @Import(HttpClientFeignConfiguration.class) class HttpClientFeignLoadBalancedConfiguration {@Bean @ConditionalOnMissingBean(Client.class) public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
SpringClientFactory clientFactory, HttpClient httpClient) { ApacheHttpClient delegate = new ApacheHttpClient(httpClient); return new LoadBalancerFeignClient(delegate, cachingFactory, clientFactory);
}
}
当然可以通过配置文件显示指定是否启用httpclient
```java
feign:
httpclient:
enabled: true
2.1.2 OkHttpClient
如果要使用okhttp,则使用如下配置
feign:
okhttp:
enabled: true
2.1.3 LoadBalancerFeignClient
该client是由spring的loadbalancer提供的实现,想要使用该客户端则可以在配置文件中加入以下配置即可。该配置默认为true,也就是服务调用默认是使用该Client。
spring:
cloud:
loadbalancer:
ribbon:
enabled: true
那么接下来看看该类的元数据有哪些
private final Client delegate;
private CachingSpringLoadBalancerFactory lbClientFactory;
private SpringClientFactory clientFactory;
可以看到内部还有一个client,那么猜测一下LoadBalancerFeignClient
只是一个代理类,而内部的client才是远程请求真正发起的地方。对于lbClientFactory
则是用来创建FeignLoadBalancer
实例的。
那么思路现在大概也清晰了,就是先通过clientFactory
拿到配置信息,随后通过lbClientFactory
生成对应的LoadBalancer实现在发起请求
IClientConfig requestConfig = getClientConfig(options, clientName);
return lbClient(clientName)
.executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse();
前面说过,内部的client才是发起http请求真正的地方,那么在executeWithLoadBalancer
方法中肯定会获取client调用execute
方法AbstractLoadBalancerAwareClient#executeWithLoadBalancer 93Line
return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
接着execute往下,调用FeignLoadBalancer
类的方法,可以看到从request参数中拿出了我们的client对象,最后调用的execute
方法。
@Override
public RibbonResponse execute(RibbonRequest request, IClientConfig configOverride)
throws IOException {
Request.Options options;
if (configOverride != null) {
RibbonProperties override = RibbonProperties.from(configOverride);
options = new Request.Options(override.connectTimeout(this.connectTimeout),
override.readTimeout(this.readTimeout));
}
else {
options = new Request.Options(this.connectTimeout, this.readTimeout);
}
Response response = request.client().execute(request.toRequest(), options);
return new RibbonResponse(request.getUri(), response);
}
该client对象就是我们之前将的ApacheHttpClient
或者OkHttpClient
,另外我们看到Options的参数是根据ribbon的配置类来的,我们知道ribbon有全局的配置,不过那个配置在这里是不会生效的,如果想配置该ribbon应该像如下配置,以前经常会碰到配置不生效的问题,这下就能知道具体的解决方法了。
# 第一种方式
# 服务名
storage:
#ribbon
ribbon:
#建立连接超时时间
ConnectTimeout: 6000
#建立连接之后,读取响应资源超时时间
ReadTimeout: 6000
# 第二种方式
feign:
client:
config:
storage:
connect-timeout: 6000
ReadTimeout: 6000
当然如果想使用全局配置的话,我们之前在分析Feign源码时,知道有个Options可以提供我们配置,那么我们在配置类中加上该参数即可,不过需要注意一点上面的配置文件优先级要高于下面这个
@Bean
public Request.Options options() {
return new Request.Options(5000, 5000);
}
2.1.4 FeignBlockingLoadBalancerClient
对于该客户端的话,其工作方式就是先选择一个服务实例,然后获取实例的请求地址发起调用,想要使用该类的话只需增加如下配置
spring:
cloud:
loadbalancer:
ribbon:
enabled: false
通过选择算法,从实例列表中获取一个客户端,熟悉ribbon的应该都了解这个逻辑
public Response execute(Request request, Request.Options options) throws IOException {
final URI originalUri = URI.create(request.url());
String serviceId = originalUri.getHost();
Assert.state(serviceId != null,
"Request URI does not contain a valid hostname: " + originalUri);
ServiceInstance instance = loadBalancerClient.choose(serviceId);
if (instance == null) {
String message = "Load balancer does not contain an instance for the service "
+ serviceId;
if (LOG.isWarnEnabled()) {
LOG.warn(message);
}
return Response.builder().request(request)
.status(HttpStatus.SERVICE_UNAVAILABLE.value())
.body(message, StandardCharsets.UTF_8).build();
}
String reconstructedUrl = loadBalancerClient.reconstructURI(instance, originalUri)
.toString();
Request newRequest = Request.create(request.httpMethod(), reconstructedUrl,
request.headers(), request.body(), request.charset(),
request.requestTemplate());
return delegate.execute(newRequest, options);
}
FeignBlockingLoadBalancerClient
的逻辑相对来说简单一点,对于超时配置可以看到使用的Feign默认的Options,如果想配置超时的话那么就像上面一样配置全局的Options的bean或者下面这种方式
feign:
client:
config:
storage:
connect-timeout: 6000
ReadTimeout: 6000
那么接下来总结一下,如果@FeignClient注解配置了url那么直接就是使用httpclient或者okhttp进行调用,如果是用的服务名,那么就会使用loadbalancer执行负载均衡策略,当然这个策略只是外面用来选择服务实例的,真正进行网络请求的还是内部的client,即httpclient或者okhttp。
2.2 contract
在spring中使用feign,可以使用spring提供的注解,例如@GetMapping("/order-tbl/create")
等,之前分析过,对于接口上的方法解析是通过contract接口解析的,那么spring同样实现了该类的接口来实现接口解析。SpringMvcContract
感兴趣可以看下该接口的实现
2.3 其余
spring还提供了其他的封装,比如拦截器BaseRequestInterceptor
等