【源码解析】OpenFeign源码之发送Http请求

阅读源码的背景

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

总结一下

  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

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值