Client组件
在Feign中,Client是一个非常重要的组件,Feign最终发送Request请求以及接收Response响应都是由Client组件来完成的。Client在Feign源码中是一个接口,在默认情况下,Client的实现类是Client.Default。Client.Default是由HttpURLConnection来实现网络请求的。另外,Client还支持HttpClient和OkHttp来进行网络请求。
Client的实现类包括:
Client.Default类:默认的Client实现类,内部使用 HttpURLConnnection 完成HTTP URL请求处理;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public static class Default implements Client { private final SSLSocketFactory sslContextFactory; private final HostnameVerifier hostnameVerifier;
public Default(SSLSocketFactory sslContextFactory, HostnameVerifier hostnameVerifier) { this.sslContextFactory = sslContextFactory; this.hostnameVerifier = hostnameVerifier; }
public Response execute(Request request, Options options) throws IOException { HttpURLConnection connection = this.convertAndSend(request, options); return this.convertResponse(connection, request); } ...... }
|
由于 HttpURLConnnection 由于缺乏连接池的支持,在达到一定流量的后服务可能会出现性能问题。
ApacheHttpClient 类:内部使用 Apache Httpclient 开源组件完成HTTP URL请求处理的Client实现类;
OkHttpClient类:内部使用 OkHttp3 开源组件完成HTTP URL请求处理的Client实现类。
自动配置 FeignAutoConfiguration
OpenFeign的自动配置类:org.springframework.cloud.openfeign.FeignAutoConfiguration,它配置的最原始的Client。
1 2 3 4 5 6 7 8
| @Configuration @ConditionalOnClass(Feign.class) @EnableConfigurationProperties({ FeignClientProperties.class, FeignHttpClientProperties.class }) public class FeignAutoConfiguration {
}
|
使用HttpClient
自动配置类中,有一个内部类HttpClientFeignConfiguration.class,它用于配置一个 ApacheHttpClient客户端。其源码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| @Configuration @ConditionalOnClass({ApacheHttpClient.class}) @ConditionalOnMissingClass({"com.netflix.loadbalancer.ILoadBalancer"}) @ConditionalOnMissingBean({CloseableHttpClient.class}) @ConditionalOnProperty( value = {"feign.httpclient.enabled"}, matchIfMissing = true ) protected static class HttpClientFeignConfiguration { private final Timer connectionManagerTimer = new Timer("FeignApacheHttpClientConfiguration.connectionManagerTimer", true); @Autowired( required = false ) private RegistryBuilder registryBuilder; private CloseableHttpClient httpClient; protected HttpClientFeignConfiguration() { } @Bean @ConditionalOnMissingBean({HttpClientConnectionManager.class}) public HttpClientConnectionManager connectionManager(ApacheHttpClientConnectionManagerFactory connectionManagerFactory, FeignHttpClientProperties httpClientProperties) { final HttpClientConnectionManager connectionManager = connectionManagerFactory.newConnectionManager(httpClientProperties.isDisableSslValidation(), httpClientProperties.getMaxConnections(), httpClientProperties.getMaxConnectionsPerRoute(), httpClientProperties.getTimeToLive(), httpClientProperties.getTimeToLiveUnit(), this.registryBuilder); this.connectionManagerTimer.schedule(new TimerTask() { public void run() { connectionManager.closeExpiredConnections(); } }, 30000L, (long)httpClientProperties.getConnectionTimerRepeat()); return connectionManager; } @Bean public CloseableHttpClient httpClient(ApacheHttpClientFactory httpClientFactory, HttpClientConnectionManager httpClientConnectionManager, FeignHttpClientProperties httpClientProperties) { RequestConfig defaultRequestConfig = RequestConfig.custom().setConnectTimeout(httpClientProperties.getConnectionTimeout()).setRedirectsEnabled(httpClientProperties.isFollowRedirects()).build(); this.httpClient = httpClientFactory.createBuilder().setConnectionManager(httpClientConnectionManager).setDefaultRequestConfig(defaultRequestConfig).build(); return this.httpClient; } @Bean @ConditionalOnMissingBean({Client.class}) public Client feignClient(HttpClient httpClient) { return new ApacheHttpClient(httpClient); } @PreDestroy public void destroy() throws Exception { this.connectionManagerTimer.cancel(); if (this.httpClient != null) { this.httpClient.close(); } } }
|
可以看到这个配置生效的条件是:
@ConditionalOnClass(ApacheHttpClient.class): 类路径下存在 ApacheHttpClient 类。
@ConditionalOnMissingClass("com.netflix.loadbalancer.ILoadBalancer"):类路径下不存在 ILoadBalancer 类。
feign.httpclient.enabled的值是true或者缺省(默认是true)。源码如下:
那么如何在Feign中使用HttpClient的框架呢?根据上面FeignAutoConfiguration.HttpClientFeignConfiguration的源码,可知只需要引入HttpClient的依赖即可,feign.httpclient.enabled可设置为true或者缺省。
1 2 3 4 5
| <dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-httpclient</artifactId> <version>9.4.0</version> </dependency>
|
使用OkHttp
自动配置类中,另外一个内部类 OkHttpFeignConfiguration.class,它用于配置一个 OkHttp 客户端。其源码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| @Configuration @ConditionalOnClass({OkHttpClient.class}) @ConditionalOnMissingClass({"com.netflix.loadbalancer.ILoadBalancer"}) @ConditionalOnMissingBean({okhttp3.OkHttpClient.class}) @ConditionalOnProperty({"feign.okhttp.enabled"}) protected static class OkHttpFeignConfiguration { private okhttp3.OkHttpClient okHttpClient; protected OkHttpFeignConfiguration() { } @Bean @ConditionalOnMissingBean({ConnectionPool.class}) public ConnectionPool httpClientConnectionPool(FeignHttpClientProperties httpClientProperties, OkHttpClientConnectionPoolFactory connectionPoolFactory) { Integer maxTotalConnections = httpClientProperties.getMaxConnections(); Long timeToLive = httpClientProperties.getTimeToLive(); TimeUnit ttlUnit = httpClientProperties.getTimeToLiveUnit(); return connectionPoolFactory.create(maxTotalConnections, timeToLive, ttlUnit); } @Bean public okhttp3.OkHttpClient client(OkHttpClientFactory httpClientFactory, ConnectionPool connectionPool, FeignHttpClientProperties httpClientProperties) { Boolean followRedirects = httpClientProperties.isFollowRedirects(); Integer connectTimeout = httpClientProperties.getConnectionTimeout(); Boolean disableSslValidation = httpClientProperties.isDisableSslValidation(); this.okHttpClient = httpClientFactory.createBuilder(disableSslValidation).connectTimeout((long)connectTimeout, TimeUnit.MILLISECONDS).followRedirects(followRedirects).connectionPool(connectionPool).build(); return this.okHttpClient; } @Bean @ConditionalOnMissingBean({Client.class}) public Client feignClient(okhttp3.OkHttpClient client) { return new OkHttpClient(client); } }
|
可以看到这个配置生效的条件是:
@ConditionalOnClass(OkHttpClient.class): 类路径下存在 OkHttpClient 类。
@ConditionalOnMissingClass("com.netflix.loadbalancer.ILoadBalancer"):类路径下不存在 ILoadBalancer 类。
feign.okhttp.enabled的值是true(默认是false)。
所以同理,如果想要在Feign中使用OkHttp作为网络请求框架,则需要在pom文件中加上OkHttp的依赖,并且feign.okhttp.enabled为ture(这项配置默认是false),代码如下:
pom.xml文件中引入依赖:
1 2 3 4 5
| <dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-okhttp</artifactId> <version>10.2.0</version> </dependency>
|
application.yml文件中增加配置项:
1 2 3
| feign: okhttp: enabled: true
|
Ribbon的负载均衡自动配置 FeignRibbonClientAutoConfiguration
除了FeignAutoConfiguration,还有一个很重要的自动配置类:org.springframework.cloud.openfeign.ribbon.FeignRibbonClientAutoConfiguration ,这个自动配置类位于Robbin的jar包中,它让Client具备负载均衡的能力。
Robbin的依赖:
1 2 3 4 5
| <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-ribbon</artifactId> </dependency>
|
下面分析一下这个类的源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import com.netflix.loadbalancer.ILoadBalancer;
@ConditionalOnClass({ILoadBalancer.class, Feign.class}) @Configuration @AutoConfigureBefore({FeignAutoConfiguration.class}) @EnableConfigurationProperties({FeignHttpClientProperties.class}) @Import({ HttpClientFeignLoadBalancedConfiguration.class, //配置:包装ApacheHttpClient实例的负载均衡客户端 OkHttpFeignLoadBalancedConfiguration.class, //配置:包装OkHttpClient 实例的负载均衡客户端 DefaultFeignLoadBalancedConfiguration.class //配置:包装Client.Default 实例的负载均衡客户端 }) public class FeignRibbonClientAutoConfiguration {
public FeignRibbonClientAutoConfiguration() { }
}
|
根据类上的注解 @AutoConfigureBefore({FeignAutoConfiguration.class}) 可知,FeignRibbonClientAutoConfiguration 自动配置是优先于 FeignAutoConfiguration 的。所以当包含OpenFeign功能的项目中引入了Robbin之后,FeignRibbonClientAutoConfiguration 自动配置将生效,而 FeignAutoConfiguration.class 的自动配置将会被忽略。
再根据类上的注解 @ConditionalOnClass({ILoadBalancer.class, Feign.class}) ,可知 FeignRibbonClientAutoConfiguration 的自动配置有两个前提条件:
- 当前的类路径中,存在
ILoadBalancer.class 类
- 当前的类路径中,存在
Feign.class 类
结论:当我们引入了Ribbon的依赖,FeignRibbonClientAutoConfiguration 自动配置生效,负载均衡功能也就生效了。
LoadBalancerFeignClient
LoadBalancerFeignClient 类:这是Ribbon jar包中一个特殊的 feign.Client 客户端实现类。内部先使用 Ribbon 负载均衡算法计算server服务器,然后使用包装的 delegate 客户端实例,去完成 HTTP URL请求处理。
FeignRibbonClientAutoConfiguration 自动配置类,并没有直接配置 LoadBalancerFeignClient 容器实例,而是使用@Import注解,通过导入其他配置类的方式,完成 LoadBalancerFeignClient 客户端容器实例的配置。
分别导入了以下三个自动配置类:
- HttpClientFeignLoadBalancedConfiguration.class
该配置类,负责配置一个包装 ApacheHttpClient 实例的 LoadBalancerFeignClient负载均衡客户端。
- OkHttpFeignLoadBalancedConfiguration.class
该配置类,负责配置一个包装 OkHttpClient 实例的 LoadBalancerFeignClient负载均衡客户端。
- DefaultFeignLoadBalancedConfiguration.class
该配置类,负责配置一个包装 Client.Default 实例的 LoadBalancerFeignClient负载均衡客户端。
所以原生的 HttpClientFeignConfiguration 配置变成了 HttpClientFeignLoadBalancedConfiguration:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| @Configuration @ConditionalOnClass(ApacheHttpClient.class) @ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true) 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); }
}
|
原生的 OkHttpFeignConfiguration 配置变成了 OkHttpFeignLoadBalancedConfiguration:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| @Configuration @ConditionalOnClass(OkHttpClient.class) @ConditionalOnProperty("feign.okhttp.enabled") class OkHttpFeignLoadBalancedConfiguration {
@Bean @ConditionalOnMissingBean(Client.class) public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory, SpringClientFactory clientFactory, okhttp3.OkHttpClient okHttpClient) { OkHttpClient delegate = new OkHttpClient(okHttpClient); return new LoadBalancerFeignClient(delegate, cachingFactory, clientFactory); }
}
|
Client实例变成了LoadBalancerFeignClient,只不过LoadBalancerFeignClient中封装了ApacheHttpClient或者OkHttp。