基于Feign使用okhttp的填坑之旅

1、由于项目需要远程调用http请求

因此就想到了Feign,因为真的非常的方便,只需要定义一个接口就行。

但是feign默认使用的JDK的URLHttpConnection,没有连接池效率不好,从Feign的自动配置类FeignAutoConfiguration中可以看到Feign除了默认的http客户端还支持okhttp和ApacheHttpClient,我这里选择了okhttp,它是有连接池的。

2、看看网络上大部分博客中是怎么使用okhttp的

1)、引入feign和okhttp的maven坐标

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-dependencies</artifactId>
      <version>${spring-cloud.version}</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

 <dependency>
   <groupId>io.github.openfeign</groupId>
   <artifactId>feign-okhttp</artifactId>
 </dependency>

2)、在配置文件中禁用默认的URLHttpConnection,启动okhttp

feign.httpclient.enabled=false
feign.okhttp.enabled=true

3)、其实这个时候就可以使用okhttp了

但网络上大部分博客还写了一个自定义配置类,在其中实例化了一个okhttp3.OkHttpClient,就是这么一个配置类导致了大坑啊,有了它之后okhttp根本不会生效,不信咱们就是来试一下

@Configuration
@ConditionalOnClass(Feign.class)
@AutoConfigureBefore(FeignAutoConfiguration.class)
public class OkHttpConfig {
  @Bean
  public okhttp3.OkHttpClient okHttpClient(){
    return new okhttp3.OkHttpClient.Builder()
        //设置连接超时
        .connectTimeout(10 , TimeUnit.SECONDS)
        //设置读超时
        .readTimeout(10 , TimeUnit.SECONDS)
        //设置写超时
        .writeTimeout(10 , TimeUnit.SECONDS)
        //是否自动重连
        .retryOnConnectionFailure(true)
        .connectionPool(new ConnectionPool(10 , 5L, TimeUnit.MINUTES))
        .build();
  }
}

上面这个配置类其实就是配置了一下okhttp的基本参数和连接池的基本参数

此时我们可以在配置文件中开始日志打印,看一下那些自动配置没有生效

debug=true

启动我们的项目可以在控制台搜索到如下日志输出

FeignAutoConfiguration.OkHttpFeignConfiguration:
   Did not match:
     - @ConditionalOnBean (types: okhttp3.OkHttpClient; SearchStrategy: all) found beans of type 'okhttp3.OkHttpClient' okHttpClient (OnBeanCondition)
   Matched:
     - @ConditionalOnClass found required class 'feign.okhttp.OkHttpClient'; @ConditionalOnMissingClass did not find unwanted class 'com.netflix.loadbalancer.ILoadBalancer' (OnClassCondition)
     - @ConditionalOnProperty (feign.okhttp.enabled) matched (OnPropertyCondition)

从日志中可以清楚的看到FeignAutoConfiguration.OkHttpFeignConfiguration没有匹配成功(Did not match),原因也很简单是因为容器中已经存在了okhttp3.OkHttpClient对象,我们去看看这个配置类的源码,其中类上标注了@ConditionalOnMissingBean(okhttp3.OkHttpClient.class),意思上当容器中不存在okhttp3.OkHttpClient对象时才生效,然后我们却在自定义的配置类中画蛇添足的实例化了一个该对象到容器中。

@Configuration(proxyBeanMethods = false)
 @ConditionalOnClass(OkHttpClient.class)
 @ConditionalOnMissingClass("com.netflix.loadbalancer.ILoadBalancer")
 @ConditionalOnMissingBean(okhttp3.OkHttpClient.class)
 @ConditionalOnProperty("feign.okhttp.enabled")
 protected static class OkHttpFeignConfiguration {
 private okhttp3.OkHttpClient okHttpClient;
 @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(connectTimeout, TimeUnit.MILLISECONDS)
   .followRedirects(followRedirects).connectionPool(connectionPool)
   .build();
  return this.okHttpClient;
 }

 @PreDestroy
 public void destroy() {
  if (this.okHttpClient != null) {
  this.okHttpClient.dispatcher().executorService().shutdown();
  this.okHttpClient.connectionPool().evictAll();
  }
 }

 @Bean
 @ConditionalOnMissingBean(Client.class)
 public Client feignClient(okhttp3.OkHttpClient client) {
  return new OkHttpClient(client);
 }
 }

4)、该如何处理才能使okhttp生效

其中我们的自定义配置类中并没有做什么特别复杂的事情,仅仅是给okhttp3.OkHttpClient和它的连接池对象设置了几个参数罢了,看看上面OkHttpFeignConfiguration类中实例化的几个类对象,其中就包含了okhttp3.OkHttpClient和ConnectionPool,从代码中不难看出它们的参数值都是从FeignHttpClientProperties获取的,因此我们只需要在配置文件中配上feign.httpclient开头的相关配置就可以了生效了。

如果我们的目的不仅仅是简单的修改几个参数值,比如需要在okhttp中添加拦截器Interceptor,这也非常简单,只需要写一个Interceptor的实现类,然后将OkHttpFeignConfiguration的内容完全复制一份到我们自定义的配置类中,并设置okhttp3.OkHttpClient的拦截器即可。

import okhttp3.Interceptor;
import okhttp3.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
public class MyOkhttpInterceptor implements Interceptor {
  Logger logger = LoggerFactory.getLogger(MyOkhttpInterceptor.class);
  @Override
  public Response intercept(Chain chain) throws IOException {
    logger.info("okhttp method:{}",chain.request().method());
    logger.info("okhttp request:{}",chain.request().body());
    return chain.proceed(chain.request());
  }
}

将自定义配置类中原有的内容去掉,复制一份OkHttpFeignConfiguration的代码做简单的修改,设置拦截器的代码如下

  @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(connectTimeout, TimeUnit.MILLISECONDS)
        .followRedirects(followRedirects).connectionPool(connectionPool)
          //这里设置我们自定义的拦截器
        .addInterceptor(new MyOkhttpInterceptor())
        .build();
    return this.okHttpClient;
  }

3、最后上两张图,Feign的动态代理使用和处理流程

补充:spring cloud feign sentinel okhttp3 gzip 压缩问题

引入pom okhttp 配置,okhttp使用连接池技术,相对feign httpUrlConnection 每次请求,创建一个连接,效率更高

    <dependency>
      <groupId>io.github.openfeign</groupId>
      <artifactId>feign-okhttp</artifactId>
    </dependency>

okhttp 开始压缩条件

增加拦截器动态删除Accept-Encoding 参数,使okhttp压缩生效

@Slf4j
public class HttpOkInterceptor implements Interceptor {
  @Override
  public Response intercept(Chain chain) throws IOException {
    Request originRequest = chain.request();
    Response response = null;
    if (StringUtils.isNotEmpty(originRequest.header("Accept-Encoding"))) {
      Request request = originRequest.newBuilder().removeHeader("Accept-Encoding").build();

      long doTime = System.nanoTime();
      response = chain.proceed(request);
      long currentTime = System.nanoTime();
      if(response != null) {
        ResponseBody responseBody = response.peekBody(1024 * 1024);
        LogUtil.info(log, String.format("接收响应: [%s] %n返回json:【%s】 %.1fms%n%s",
            response.request().url(),
            responseBody.string(),
            (currentTime - doTime) / 1e6d,
            response.headers()));
      }else {
        String encodedPath = originRequest.url().encodedPath();
        LogUtil.info(log, String.format("接收响应: [%s] %n %.1fms%n",
            encodedPath,
            (currentTime - doTime) / 1e6d));
      }
    }
    return response;
	}
}

feign 配置

feign:
 sentinel:
  # 开启Sentinel对Feign的支持
  enabled: true
 httpclient:
  enabled: false
 okhttp:
  enabled: true

feign 配置类

@Configuration
@ConditionalOnClass(Feign.class)
@AutoConfigureBefore(FeignAutoConfiguration.class)
public class FeignOkHttpConfig {
  @Bean
  public okhttp3.OkHttpClient okHttpClient(){
    return new okhttp3.OkHttpClient.Builder()
        //设置连接超时
        .connectTimeout(10, TimeUnit.SECONDS)
        //设置读超时
        .readTimeout(10, TimeUnit.SECONDS)
        //设置写超时
        .writeTimeout(10, TimeUnit.SECONDS)
        //是否自动重连
        .retryOnConnectionFailure(true)
        .connectionPool(new ConnectionPool(10, 5L, TimeUnit.MINUTES))
        .build();
  }
}

案例:feign client

@FeignClient(name = "服务名称",fallbackFactory = FeignFallBack.class ,url = "调试地址", configuration =FeignConfiguration.class)
public interface FeignService {
  @RequestMapping(value = "/test/updateXx", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
  public ResponseEntity<byte[]> updateXx(@RequestBody XxVo xXVo);
}

不知为啥 sentinel feign默认http,对压缩支持不好,使用okhttp 代替实现

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。如有错误或未考虑完全的地方,望不吝赐教。

(0)

相关推荐

  • 使用okhttp替换Feign默认Client的操作

    一 关键pom <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Spring Cloud OpenFeign的Starter的依赖 --> <dependency> &l

  • SpringCloud Open feign 使用okhttp 优化详解

    我就废话不多说了,大家还是直接看代码吧~ <!--web 模块 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <!--排除tomcat依赖 --> <exclusion> <artifactId&

  • SpringBoot 配置 okhttp3的操作

    1. Maven 添加依赖 <dependency> <groupId>com.squareup.okhttp3</groupId> <artifactId>okhttp</artifactId> <version>3.10.0</version> </dependency> 2. application.properties 配置文件 ok.http.connect-timeout=30 ok.http.re

  • 基于Feign使用okhttp的填坑之旅

    1.由于项目需要远程调用http请求 因此就想到了Feign,因为真的非常的方便,只需要定义一个接口就行. 但是feign默认使用的JDK的URLHttpConnection,没有连接池效率不好,从Feign的自动配置类FeignAutoConfiguration中可以看到Feign除了默认的http客户端还支持okhttp和ApacheHttpClient,我这里选择了okhttp,它是有连接池的. 2.看看网络上大部分博客中是怎么使用okhttp的 1).引入feign和okhttp的mav

  • 基于log4j2.properties踩坑与填坑

    目录 log4j2.properties踩坑与填坑 日志配置 采坑 格式化日志输出参数 记录一份自己的配置文件 Log4j2 properties配置文件 log4j2.properties踩坑与填坑 日志配置 门面模式:slf4j 日志库:log4j2 引入依赖:compile('org.springframework.boot:spring-boot-starter-log4j2:2.0.4.RELEASE') 采坑 启动Application时,出现Multiple bindings we

  • Android—基于微信开放平台v3SDK开发(微信支付填坑)

    接触微信支付之前听说过这是一个坑,,,心里已经有了准备...我以为我没准跳坑出不来了,没有想到我填上了,调用成功之后我感觉公司所有的同事都是漂亮的,隔着北京的大雾霾我仿佛看见了太阳~~~好了,装逼结束...进入正题 开发准备: 1.在微信开放平台申请账号 2.成功后创建应用,就是填一些看似很官方很正经的资料了...(说审核7天左右,没有意外的情况下你的app第二天就审核成功了是不是很开心,有了appid,是不是就可以调用微   信支付了????-------想多了,真的) 3.微信支付是需要额外

  • 剖析SpringCloud Feign中所隐藏的坑

    目录 背景 Debug Feign 的实现 总结 背景 前段时间同事碰到一个问题,需要在 SpringCloud 的 Feign 调用中使用自定义的 URL:通常情况下是没有这个需求的:毕竟都用了 SpringCloud 的了,那服务之间的调用都是走注册中心的,不会需要自定义 URL 的情况. 但也有特殊的,比如我们这里碰到 ToB 场景,需要对每个商户自定义的 URL 进行调用. 虽说也可以使用原生的 Feign 甚至是自定义一个 OKHTTP Client 实现,但这些方案都得换一种写法:

  • 浅谈mint-ui 填坑之路

    近期上手vue的移动端项目,舍弃了之前自己相对熟悉的mui框架,改为用饿了么团队为了vue量身定做的mint-ui框架. 之前开发的时候觉得mui的文档就足够坑爹了,但当我开始阅读mint-ui这个文档后才发现自己真是太年轻了... 针对一些自己遇到的问题,特此记录成文档,方便日后使用. swipe组件 因为项目加载eslint的缘故也就没有像之前的项目一样引用swiper框架. 这个轮播图的组件文档实在是不敢恭维(尽管其他的文档也好不到哪里去),官方给出的参数真是少的可怜,一些方法也并没有提到

  • AngularJS操作键值对象类似java的hashmap(填坑小结)

    前言: 我们知道java的hashmap中使用最多的是put(...),get(...)以及remove()方法,那么在angularJS中如何创造(使用)这样一个对象呢 思路分析: 我们知道在java中可以采用链式访问和"[]"访问hashmap的某一个值 具体实现: 链式访问: .factory('ParamsServices', function () { var params = {}; return { get: function (key) { return params.

  • JavaScript代码编写中各种各样的坑和填坑方法

    坑"这个字,在此的意思是"陷阱".由于 JavaScript "弱语言"的性质,使得其在使用过程中异常的宽松灵活,但也极为容易"中招".这些坑往往隐藏着,所以必须擦亮双眼,才能在学习与应用 JS 的道路上走的一帆风顺. 一.全局变量 JavaScript 通过函数管理作用域.在函数内部声明的变量只在这个函数内部,函数外面不可用.另一方面,全局变量就是在任何函数外面声明的或是未声明直接简单使用的. "未声明直接简单使用"

  • Android中WebView的基本配置与填坑记录大全

    前言 在应用程序开发过程中,经常会采用webview来展现某些界面,这样就可以不受发布版本控制,实时更新,遇到问题可以快速修复. 但是在Android开发中,由于Android版本分化严重,每一个版本针对webview都有部分更改,因此在开发过程中会遇到各种各样的坑,下面这篇就来给大家介绍关于Android中WebView的基本配置与填坑记录,话不多说了,来一起看看详细的介绍吧. 基本配置 // 硬件加速 getActivity().getWindow().setFlags( WindowMan

  • 详解centos7上elastic search安装及填坑记

    本文介绍了centos7上elastic search安装及填坑记,分享给大家,具体如下: 下载elastic search 5.3.0 wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-5.3.0.tar.gz mv elasticsearch-5.3.0.tar.gz /opt cd /opt tar -xzvf elasticsearch-5.3.0.tar.gz cd elasticsearch

  • Android Studio3.0.1填坑笔记

    从前听大神同事强老师说IntelliJ IDEA 功能强大,是Jet Brains 公司开发商业IDE(集成开发环境),同时支持Java, Scala 和Groovy.商业IDE即IntelliJ 是一款收费的IDE,当然了其实也有免费的社区版本,但是很多功能都被阉割了.IntelliJ 除了支持Android项目开发,还可以搭建java web 开发环境,功能比AS更强大. 阿拉最近重装了电脑,想着干脆也装个IntelliJ 吧,但是另一个同事说Intellij与AS会冲突.一开始我觉得是他太

随机推荐