【源码解析】OpenFeign源码之发送Http请求
迪丽瓦拉
2025-05-28 20:16:03
0

阅读源码的背景

最近在使用 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);}
}

当项目中不存在HttpClientOkHttp3的jar包的时候,会往容器中注入DefaultFeignLoadBalancedConfiguration中定义的Client,类型是LoadBalancerFeignClient

@Configuration
class DefaultFeignLoadBalancedConfiguration {DefaultFeignLoadBalancedConfiguration() {}@Bean@ConditionalOnMissingBeanpublic 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 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 apply(Target key) {List metadata = this.contract.parseAndValidatateMetadata(key.type());Map 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 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;}

总结一下

  1. FeignClientFactoryBean生成代理类的时候,会将连接时长和超时时间封装成Options对象,在执行Feign.Builder#build的时候,组装ParseHandlersByName对象构建ReflectiveFeign对象。
  2. 封装@FeignClient对象的每一个方法,用到了SynchronousMethodHandler,该对象是SynchronousMethodHandler.Factory#create通过Options来创建的。
  3. 当请求进来的时候,会执行SynchronousMethodHandler#executeAndDecode方法,把SynchronousMethodHandlerOptions对象的数据,传入到client中封装HttpURLConnection对象。
  4. 而Client的自动配置,是根据FeignRibbonClientAutoConfiguration引入的三个配置类来决定的。如果项目中有HttpClient的类,会自动引入HttpClientFeignLoadBalancedConfiguration,从而引入有HttpClient代理的LoadBalancerFeignClient

在这里插入图片描述

相关内容