热门标签 | HotTags
当前位置:  开发笔记 > 前端 > 正文

基于Feign使用okhttp的填坑之旅

这篇文章主要介绍了基于Feign使用okhttp的填坑之旅,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧

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

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

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

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

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


  
    
      org.springframework.cloud
      spring-cloud-dependencies
      ${spring-cloud.version}
      pom
      import
    
  

 

  org.springframework.cloud
  spring-cloud-starter-openfeign

 
 
   io.github.openfeign
   feign-okhttp
 

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 每次请求,创建一个连接,效率更高

    
      io.github.openfeign
      feign-okhttp
    

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 updateXx(@RequestBody XxVo xXVo);  
}

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

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


推荐阅读
  • 标题: ... [详细]
  • 在Docker中,将主机目录挂载到容器中作为volume使用时,常常会遇到文件权限问题。这是因为容器内外的UID不同所导致的。本文介绍了解决这个问题的方法,包括使用gosu和suexec工具以及在Dockerfile中配置volume的权限。通过这些方法,可以避免在使用Docker时出现无写权限的情况。 ... [详细]
  • YOLOv7基于自己的数据集从零构建模型完整训练、推理计算超详细教程
    本文介绍了关于人工智能、神经网络和深度学习的知识点,并提供了YOLOv7基于自己的数据集从零构建模型完整训练、推理计算的详细教程。文章还提到了郑州最低生活保障的话题。对于从事目标检测任务的人来说,YOLO是一个熟悉的模型。文章还提到了yolov4和yolov6的相关内容,以及选择模型的优化思路。 ... [详细]
  • 本文介绍了在开发Android新闻App时,搭建本地服务器的步骤。通过使用XAMPP软件,可以一键式搭建起开发环境,包括Apache、MySQL、PHP、PERL。在本地服务器上新建数据库和表,并设置相应的属性。最后,给出了创建new表的SQL语句。这个教程适合初学者参考。 ... [详细]
  • 云原生边缘计算之KubeEdge简介及功能特点
    本文介绍了云原生边缘计算中的KubeEdge系统,该系统是一个开源系统,用于将容器化应用程序编排功能扩展到Edge的主机。它基于Kubernetes构建,并为网络应用程序提供基础架构支持。同时,KubeEdge具有离线模式、基于Kubernetes的节点、群集、应用程序和设备管理、资源优化等特点。此外,KubeEdge还支持跨平台工作,在私有、公共和混合云中都可以运行。同时,KubeEdge还提供数据管理和数据分析管道引擎的支持。最后,本文还介绍了KubeEdge系统生成证书的方法。 ... [详细]
  • 阿,里,云,物,联网,net,core,客户端,czgl,aliiotclient, ... [详细]
  • 本文介绍了Hyperledger Fabric外部链码构建与运行的相关知识,包括在Hyperledger Fabric 2.0版本之前链码构建和运行的困难性,外部构建模式的实现原理以及外部构建和运行API的使用方法。通过本文的介绍,读者可以了解到如何利用外部构建和运行的方式来实现链码的构建和运行,并且不再受限于特定的语言和部署环境。 ... [详细]
  • 使用在线工具jsonschema2pojo根据json生成java对象
    本文介绍了使用在线工具jsonschema2pojo根据json生成java对象的方法。通过该工具,用户只需将json字符串复制到输入框中,即可自动将其转换成java对象。该工具还能解析列表式的json数据,并将嵌套在内层的对象也解析出来。本文以请求github的api为例,展示了使用该工具的步骤和效果。 ... [详细]
  • 本文介绍了一些Java开发项目管理工具及其配置教程,包括团队协同工具worktil,版本管理工具GitLab,自动化构建工具Jenkins,项目管理工具Maven和Maven私服Nexus,以及Mybatis的安装和代码自动生成工具。提供了相关链接供读者参考。 ... [详细]
  • 单点登录原理及实现方案详解
    本文详细介绍了单点登录的原理及实现方案,其中包括共享Session的方式,以及基于Redis的Session共享方案。同时,还分享了作者在应用环境中所遇到的问题和经验,希望对读者有所帮助。 ... [详细]
  • Spring常用注解(绝对经典),全靠这份Java知识点PDF大全
    本文介绍了Spring常用注解和注入bean的注解,包括@Bean、@Autowired、@Inject等,同时提供了一个Java知识点PDF大全的资源链接。其中详细介绍了ColorFactoryBean的使用,以及@Autowired和@Inject的区别和用法。此外,还提到了@Required属性的配置和使用。 ... [详细]
  • docker增加restart=always, docker重启后自动启动容器的方法
    本文介绍了在运行docker容器时如何添加参数来保证每次docker服务重启后容器也自动重启的方法,以及如何使用命令来更新已启动的容器。 ... [详细]
  • Tomcat/Jetty为何选择扩展线程池而不是使用JDK原生线程池?
    本文探讨了Tomcat和Jetty选择扩展线程池而不是使用JDK原生线程池的原因。通过比较IO密集型任务和CPU密集型任务的特点,解释了为何Tomcat和Jetty需要扩展线程池来提高并发度和任务处理速度。同时,介绍了JDK原生线程池的工作流程。 ... [详细]
  • 集合的遍历方式及其局限性
    本文介绍了Java中集合的遍历方式,重点介绍了for-each语句的用法和优势。同时指出了for-each语句无法引用数组或集合的索引的局限性。通过示例代码展示了for-each语句的使用方法,并提供了改写为for语句版本的方法。 ... [详细]
  • 本文讨论了微软的STL容器类是否线程安全。根据MSDN的回答,STL容器类包括vector、deque、list、queue、stack、priority_queue、valarray、map、hash_map、multimap、hash_multimap、set、hash_set、multiset、hash_multiset、basic_string和bitset。对于单个对象来说,多个线程同时读取是安全的。但如果一个线程正在写入一个对象,那么所有的读写操作都需要进行同步。 ... [详细]
author-avatar
寻找冄己靉
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有