阅读源码的背景
最近在使用 Feign的时候碰到了请求超时的问题,借着这个事儿好好的梳理下与 Feign 相关的客户端,以及客户端的配置。另外,如果想要用HttpClient
代替HttpURLConnection
来提升性能,通过阅读源码可以掌握Feign是如何实现Http请求发送的自动配置。Feign远程调用的整个框架的运行原理可以参考Feign远程调用的底层原理。
配置默认的连接时长
feign:
client:
config:
default:
connectTimeout: 10000 #单位毫秒
readTimeout: 10000 #单位毫秒
配置指定服务的连接时长
feign:
client:
config:
app-client: # app-client服务名
connectTimeout: 10000 #单位毫秒
readTimeout: 10000 #单位毫秒
实现原理
FeignRibbonClientAutoConfiguration
该类优先注入HttpClientFeignLoadBalancedConfiguration
,其次注入OkHttpFeignLoadBalancedConfiguration
,最后注入DefaultFeignLoadBalancedConfiguration
。
@Import({HttpClientFeignLoadBalancedConfiguration.class, OkHttpFeignLoadBalancedConfiguration.class, DefaultFeignLoadBalancedConfiguration.class})
public class FeignRibbonClientAutoConfiguration {
public FeignRibbonClientAutoConfiguration() {
}
HttpClientFeignLoadBalancedConfiguration
注入的条件是存在类ApacheHttpClient
@Configuration
@ConditionalOnClass({ApacheHttpClient.class})
@ConditionalOnProperty(
value = {"feign.httpclient.enabled"},
matchIfMissing = true
)
class HttpClientFeignLoadBalancedConfiguration {
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
和OkHttp3
的jar包的时候,会往容器中注入DefaultFeignLoadBalancedConfiguration
中定义的Client
,类型是LoadBalancerFeignClient
。
@Configuration
class DefaultFeignLoadBalancedConfiguration {
DefaultFeignLoadBalancedConfiguration() {
}
@Bean
@ConditionalOnMissingBean
public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory, SpringClientFactory clientFactory) {
return new LoadBalancerFeignClient(new Default((SSLSocketFactory)null, (HostnameVerifier)null), cachingFactory, clientFactory);
}
}
FeignClientFactoryBean
FeignClientFactoryBean#configureFeign
,用来配置Feign。先用Feign的默认配置,后用Feign的定制化配置。
protected void configureFeign(FeignContext context, Builder builder) {
FeignClientProperties properties = (FeignClientProperties)this.applicationContext.getBean(FeignClientProperties.class);
if (properties != null) {
if (properties.isDefaultToProperties()) {
this.configureUsingConfiguration(context, builder);
this.configureUsingProperties((FeignClientConfiguration)properties.getConfig().get(properties.getDefaultConfig()), builder);
this.configureUsingProperties((FeignClientConfiguration)properties.getConfig().get(this.contextId), builder);
} else {
this.configureUsingProperties((FeignClientConfiguration)properties.getConfig().get(properties.getDefaultConfig()), builder);
this.configureUsingProperties((FeignClientConfiguration)properties.getConfig().get(this.contextId), builder);
this.configureUsingConfiguration(context, builder);
}
} else {
this.configureUsingConfiguration(context, builder);
}
}
protected void configureUsingProperties(FeignClientConfiguration config, Builder builder) {
if (config != null) {
if (config.getLoggerLevel() != null) {
builder.logLevel(config.getLoggerLevel());
}
if (config.getConnectTimeout() != null && config.getReadTimeout() != null) {
builder.options(new Options(config.getConnectTimeout(), config.getReadTimeout()));
}
if (config.getRetryer() != null) {
Retryer retryer = (Retryer)this.getOrInstantiate(config.getRetryer());
builder.retryer(retryer);
}
if (config.getErrorDecoder() != null) {
ErrorDecoder errorDecoder = (ErrorDecoder)this.getOrInstantiate(config.getErrorDecoder());
builder.errorDecoder(errorDecoder);
}
if (config.getRequestInterceptors() != null && !config.getRequestInterceptors().isEmpty()) {
Iterator var7 = config.getRequestInterceptors().iterator();
while(var7.hasNext()) {
Class<RequestInterceptor> bean = (Class)var7.next();
RequestInterceptor interceptor = (RequestInterceptor)this.getOrInstantiate(bean);
builder.requestInterceptor(interceptor);
}
}
if (config.getDecode404() != null && config.getDecode404()) {
builder.decode404();
}
if (Objects.nonNull(config.getEncoder())) {
builder.encoder((Encoder)this.getOrInstantiate(config.getEncoder()));
}
if (Objects.nonNull(config.getDecoder())) {
builder.decoder((Decoder)this.getOrInstantiate(config.getDecoder()));
}
if (Objects.nonNull(config.getContract())) {
builder.contract((Contract)this.getOrInstantiate(config.getContract()));
}
}
}
Feign.Builder#build
,建造者模式,根据builder对象生成ReflectiveFeign
。
public Feign build() {
Factory synchronousMethodHandlerFactory = new Factory(this.client, this.retryer, this.requestInterceptors, this.logger, this.logLevel, this.decode404, this.closeAfterDecode, this.propagationPolicy);
ParseHandlersByName handlersByName = new ParseHandlersByName(this.contract, this.options, this.encoder, this.decoder, this.queryMapEncoder, this.errorDecoder, synchronousMethodHandlerFactory);
return new ReflectiveFeign(handlersByName, this.invocationHandlerFactory, this.queryMapEncoder);
}
ReflectiveFeign.ParseHandlersByName#apply
,给FeignClient
中的每一个方法进行解析,并且创造MethodHandler
来作为方法的代理。
public Map<String, MethodHandler> apply(Target key) {
List<MethodMetadata> metadata = this.contract.parseAndValidatateMetadata(key.type());
Map<String, MethodHandler> result = new LinkedHashMap();
MethodMetadata md;
Object buildTemplate;
for(Iterator var4 = metadata.iterator(); var4.hasNext(); result.put(md.configKey(), this.factory.create(key, md, (Factory)buildTemplate, this.options, this.decoder, this.errorDecoder))) {
md = (MethodMetadata)var4.next();
if (!md.formParams().isEmpty() && md.template().bodyTemplate() == null) {
buildTemplate = new ReflectiveFeign.BuildFormEncodedTemplateFromArgs(md, this.encoder, this.queryMapEncoder);
} else if (md.bodyIndex() != null) {
buildTemplate = new ReflectiveFeign.BuildEncodedTemplateFromArgs(md, this.encoder, this.queryMapEncoder);
} else {
buildTemplate = new ReflectiveFeign.BuildTemplateByResolvingArgs(md, this.queryMapEncoder);
}
}
return result;
}
}
SynchronousMethodHandler.Factory#create
,创造方法维度的代理类。
public MethodHandler create(Target<?> target, MethodMetadata md, feign.RequestTemplate.Factory buildTemplateFromArgs, Options options, Decoder decoder, ErrorDecoder errorDecoder) {
return new SynchronousMethodHandler(target, this.client, this.retryer, this.requestInterceptors, this.logger, this.logLevel, md, buildTemplateFromArgs, options, decoder, errorDecoder, this.decode404, this.closeAfterDecode, this.propagationPolicy);
}
SynchronousMethodHandler
SynchronousMethodHandler#executeAndDecode
,调用client的execute方法,该client对象是LoadBalancerFeignClient
。
Object executeAndDecode(RequestTemplate template) throws Throwable {
Request request = this.targetRequest(template);
if (this.logLevel != Level.NONE) {
this.logger.logRequest(this.metadata.configKey(), this.logLevel, request);
}
long start = System.nanoTime();
Response response;
try {
response = this.client.execute(request, this.options);
} catch (IOException var15) {
if (this.logLevel != Level.NONE) {
this.logger.logIOException(this.metadata.configKey(), this.logLevel, var15, this.elapsedTime(start));
}
throw FeignException.errorExecuting(request, var15);
}
//...
}
Client.Default
Client接口的默认实现类。创造HttpURLConnection
对象后,设置连接时长和超时时长。
public Response execute(Request request, Options options) throws IOException {
HttpURLConnection connection = this.convertAndSend(request, options);
return this.convertResponse(connection, request);
}
HttpURLConnection convertAndSend(Request request, Options options) throws IOException {
HttpURLConnection connection = (HttpURLConnection)(new URL(request.url())).openConnection();
if (connection instanceof HttpsURLConnection) {
HttpsURLConnection sslCon = (HttpsURLConnection)connection;
if (this.sslContextFactory != null) {
sslCon.setSSLSocketFactory(this.sslContextFactory);
}
if (this.hostnameVerifier != null) {
sslCon.setHostnameVerifier(this.hostnameVerifier);
}
}
connection.setConnectTimeout(options.connectTimeoutMillis());
connection.setReadTimeout(options.readTimeoutMillis());
connection.setAllowUserInteraction(false);
connection.setInstanceFollowRedirects(options.isFollowRedirects());
connection.setRequestMethod(request.httpMethod().name());
Collection<String> contentEncodingValues = (Collection)request.headers().get("Content-Encoding");
boolean gzipEncodedRequest = contentEncodingValues != null && contentEncodingValues.contains("gzip");
boolean deflateEncodedRequest = contentEncodingValues != null && contentEncodingValues.contains("deflate");
boolean hasAcceptHeader = false;
Integer contentLength = null;
Iterator var9 = request.headers().keySet().iterator();
while(var9.hasNext()) {
String field = (String)var9.next();
if (field.equalsIgnoreCase("Accept")) {
hasAcceptHeader = true;
}
Iterator var11 = ((Collection)request.headers().get(field)).iterator();
while(var11.hasNext()) {
String value = (String)var11.next();
if (field.equals("Content-Length")) {
if (!gzipEncodedRequest && !deflateEncodedRequest) {
contentLength = Integer.valueOf(value);
connection.addRequestProperty(field, value);
}
} else {
connection.addRequestProperty(field, value);
}
}
}
if (!hasAcceptHeader) {
connection.addRequestProperty("Accept", "*/*");
}
if (request.requestBody().asBytes() != null) {
if (contentLength != null) {
connection.setFixedLengthStreamingMode(contentLength);
} else {
connection.setChunkedStreamingMode(8196);
}
connection.setDoOutput(true);
OutputStream out = connection.getOutputStream();
if (gzipEncodedRequest) {
out = new GZIPOutputStream((OutputStream)out);
} else if (deflateEncodedRequest) {
out = new DeflaterOutputStream((OutputStream)out);
}
try {
((OutputStream)out).write(request.requestBody().asBytes());
} finally {
try {
((OutputStream)out).close();
} catch (IOException var18) {
}
}
}
return connection;
}
总结一下
- 在
FeignClientFactoryBean
生成代理类的时候,会将连接时长和超时时间封装成Options
对象,在执行Feign.Builder#build
的时候,组装ParseHandlersByName
对象构建ReflectiveFeign
对象。 - 封装
@FeignClient
对象的每一个方法,用到了SynchronousMethodHandler
,该对象是SynchronousMethodHandler.Factory#create
通过Options
来创建的。 - 当请求进来的时候,会执行
SynchronousMethodHandler#executeAndDecode
方法,把SynchronousMethodHandler
的Options
对象的数据,传入到client中封装HttpURLConnection
对象。 - 而Client的自动配置,是根据
FeignRibbonClientAutoConfiguration
引入的三个配置类来决定的。如果项目中有HttpClient
的类,会自动引入HttpClientFeignLoadBalancedConfiguration
,从而引入有HttpClient
代理的LoadBalancerFeignClient
。