SpringCloud—OpenFeign的Http请求源码简析

Client组件

在Feign中,Client是一个非常重要的组件,Feign最终发送Request请求以及接收Response响应都是由Client组件来完成的。Client在Feign源码中是一个接口,在默认情况下,Client的实现类是Client.DefaultClient.Default是由HttpURLConnection来实现网络请求的。另外,Client还支持HttpClient和OkHttp来进行网络请求。

Client的实现类包括:

  1. 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 由于缺乏连接池的支持,在达到一定流量的后服务可能会出现性能问题。

  2. ApacheHttpClient 类:内部使用 Apache Httpclient 开源组件完成HTTP URL请求处理的Client实现类;

  3. 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) {
//Client实例是一个ApacheHttpClient对象
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) {
//Client实例是一个OkHttpClient对象
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
<!-- ribbon-->
<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() {
}

//...省略
}

  1. 根据类上的注解 @AutoConfigureBefore({FeignAutoConfiguration.class}) 可知,FeignRibbonClientAutoConfiguration 自动配置是优先于 FeignAutoConfiguration 的。所以当包含OpenFeign功能的项目中引入了Robbin之后,FeignRibbonClientAutoConfiguration 自动配置将生效,而 FeignAutoConfiguration.class 的自动配置将会被忽略。

  2. 再根据类上的注解 @ConditionalOnClass({ILoadBalancer.class, Feign.class}) ,可知 FeignRibbonClientAutoConfiguration 的自动配置有两个前提条件:

    1. 当前的类路径中,存在 ILoadBalancer.class
    2. 当前的类路径中,存在 Feign.class

结论:当我们引入了Ribbon的依赖,FeignRibbonClientAutoConfiguration 自动配置生效,负载均衡功能也就生效了。

LoadBalancerFeignClient

LoadBalancerFeignClient 类:这是Ribbon jar包中一个特殊的 feign.Client 客户端实现类。内部先使用 Ribbon 负载均衡算法计算server服务器,然后使用包装的 delegate 客户端实例,去完成 HTTP URL请求处理。

FeignRibbonClientAutoConfiguration 自动配置类,并没有直接配置 LoadBalancerFeignClient 容器实例,而是使用@Import注解,通过导入其他配置类的方式,完成 LoadBalancerFeignClient 客户端容器实例的配置。

分别导入了以下三个自动配置类:

  1. HttpClientFeignLoadBalancedConfiguration.class
    该配置类,负责配置一个包装 ApacheHttpClient 实例的 LoadBalancerFeignClient负载均衡客户端。
  2. OkHttpFeignLoadBalancedConfiguration.class
    该配置类,负责配置一个包装 OkHttpClient 实例的 LoadBalancerFeignClient负载均衡客户端。
  3. 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。

------ 本文完 ------