草庐IT

07-Feign远程调用

OnlyOnYourself-lzw 2023-03-28 原文

二、Feign远程调用

  • 之前利用RestTemplate发起远程调用的代码
  • 存在下面的问题
    • 代码可读性差,编程体验不统一
    • 参数复杂URL难以维护
  • Feign是一个声明式的http客户端,官方地址如下所示
  • 其作用就是帮助我们优雅地实现http请求的发送,解决上面提到的问题

2.1、Feign替代RestTemplate

2.1.1、引入依赖

  • 在order-service服务的pom文件中引入feign的依赖

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

2.1.2、添加注解

  • 在order-service的启动类添加注解开启Feign的功能

  • 可以注释掉以下代码

    • @Bean
      @LoadBalanced
      public RestTemplate restTemplate() {
          return new RestTemplate();
      }
      

2.1.3、编写Feign的客户端

①、在order-service中创建一个包client

②、新建一个接口

  • 1)指定服务的名字

  • 2)指定访问地址

  • 3)方法参数的注入

  • package cn.coolman.order.client;
    
    
    import cn.coolman.order.pojo.User;
    import org.springframework.cloud.openfeign.FeignClient;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    
    @FeignClient("userservice")     // 指定服务的名字
    public interface UserFeignClient {
    
        @GetMapping("/user/{id}")
        User findById(@PathVariable("id") Long id);
    }
    
    

③、修改OrderController的代码,使用Feign的客户端去调用

  • package cn.coolman.order.web;
    
    import cn.coolman.order.client.UserFeignClient;
    import cn.coolman.order.pojo.Order;
    import cn.coolman.order.pojo.User;
    import cn.coolman.order.service.OrderService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    import org.springframework.web.client.RestTemplate;
    
    @RestController
    @RequestMapping("order")
    public class OrderController {
    
       @Autowired
       private OrderService orderService;
    
    //   @Autowired
    //   private RestTemplate restTemplate;
    
        @Autowired
        private UserFeignClient userFeignClient;
    
        @GetMapping("{orderId}")
        public Order queryOrderByUserId(@PathVariable("orderId") Long orderId) {
            // 根据id查询订单并返回
            Order order = orderService.queryOrderById(orderId);
    
    //        User user = restTemplate.getForObject("http://localhost:8081/user/" + order.getUserId(), User.class);
    
    //        User user = restTemplate.getForObject("http://userservice/user/" + order.getUserId(), User.class);
    
            User user = userFeignClient.findById(order.getUserId());
            order.setUser(user);
    
            return order;
        }
    }
    
    

④、小结

  • 这个客户端主要是基于SpringMVC的注解来声明远程调用的信息,比如:
    • 服务名称:userservice
    • 请求方式:GET
    • 请求路径:/user/
    • 请求参数:Long id
    • 返回值类型:User
  • 这样,Feign就可以帮助我们发送http请求,无需自己使用RestTemplate来发送请求了

2.1.4、测试

  • 修改order-service中的OrderService类中的queryOrderByUserId方法,使用Feign客户端代替RestTemplate
  • 访问浏览器:http://localhost:8080/order/101

2.1.5、小结

  • 使用Feign的步骤
    • ①、引入依赖
    • ②、在启动类上添加@EnableFeignClients注解,启动Feign服务
    • ③、编写FeignClient接口(目标方法的路径,服务的名称)
    • ④、使用FeignClient中定义的方法代替RestTemplate
  • 查看依赖可以知道Feign底层是以来了Ribbon的包

2.2、自定义配置

  • Feign可以支持很多的自定义配置,如下表所示

    • 类型 作用 说明
      feign.Logger.Level 修改日志级别 包含四种不同的级别:NONE(默认)、BASIC(记录什么时候发出、什么时候结束)、HEADERS(在basic基础还记录请求头信息)、FULL(记录所有的信息)
      feign.codec.Decoder 响应结果的解析器 http远程调用的结果做解析,例如解析json字符串为java对象
      feign.codec.Encoder 请求参数编码 将请求参数编码,便于通过http请求发送
      feign.Contract 支持的注解格式 默认是SpringMVC的注解
      feign.Retryer 失败重试机制 请求失败的重试机制,默认是没有,不过会使用Ribbon的重试
    • 一般情况下,默认值就能满足正常使用,如果需要自定义,只需要创建自定义@Bean覆盖默认Bean即可

  • 下面以日志为例来演示如何自定义配置

2.2.1、配置文件方式

  • 以修改order-service为例

    • ①、基于配置文件修改Feign的日志级别可以针对单个要调用的服务

      • feign:  
          client:
            config: 
              userservice:  # 针对某个微服务的配置
                loggerLevel: BASIC #  日志级别 
        
    • ②、也可以针对所有服务

      • feign:  
          client:
            config: 
              default: # 这里用default就是全局配置,如果是写服务名称,则是针对某个微服务的配置
                loggerLevel: FULL #  日志级别 
        
    • PS:配置default.loggerLevel的时候idea中没有提示信息,如果两个都要配置,则优先使用userservice

  • 要看到输出的日志信息,需要将系统日志级别设置为debug才能看到

    • logging:
        level:
          cn.coolman: debug
      
    • feign:
        client:
          config:
            userservice:
              loggerLevel: BASIC
            default:
              loggerLevel: FULL
      
  • 日志的级别分为四种

    • NONE:不记录任何日志信息,这是默认值
    • BASIC:仅记录请求的方法,URL以及响应状态码和执行事件
    • HEADERS:在BASIC的基础上,额外记录了请求和响应头信息
    • FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据
  • 配置完成后,重启服务,在浏览器访问一次,对比以下配置前和配置后的控制台输出信息

    • 配置前
    • 配置后

2.2.2、Java代码方式

将上面的配置注释

  • ①、也可以基于Java代码来修改日志级别,先声明一个类放在config包中,然后声明一个Logger.Level的对象

    • package cn.coolman.order.config;
      
      import feign.Logger;
      import org.springframework.context.annotation.Bean;
      
      public class DefaultFeignConfiguration {
      
          @Bean
          public Logger.Level loggerLevel() {
              // 日志级别为BASIC,这是枚举类型
              return Logger.Level.BASIC;
          }
      }
      
      
  • ②、如果要全局生效,将其放到OrderApplication启动类的@EnableFeignClient这个注解中

    • @EnableFeignClients(defaultConfiguration = DefaultFeignConfiguration.class) 
      
  • ③、如果是局部生效,则把它放到UserClient接口对应的@FeignClient这个注解中,将上面OrderApplication启动类的defaultConfiguration部分去掉

    • @FeignClient(value = "userservice", configuration = DefaultFeignConfiguration.class) 
      
  • ④、查看控制台输出信息

2.2.3、小结

  • Feign日志的类型
    • NONE:默认,不记录日志
    • BASIC:仅记录请求的方法,URL以及响应状态码和执行时间
    • HEADERS:在BASIC基础上添加请求头
    • FULL:记录全部
  • 如何实现Feign的日志记录
    • yml文件配置
    • 编码方式配置

2.3、Feign使用优化

  • Feign底层发起http请求,依赖于其他的框架,其底层客户端实现包括
    • URLConnection:默认实现,不支持连接池。每次出啊关键连接要三次握手,结束连接还要四次挥手
    • Apache HttpClient:支持连接池
    • OKHttp:支持连接池
  • 因此提高Feign的性能的主要手段就是使用连接池代替默认的URLConnection

2.3.1、Apache的HttpClient

  • 1)引入依赖

    • 在order-service的pom文件中引入Apache的HttpClient依赖

    • <!--httpClient的依赖 -->
      <dependency>
          <groupId>io.github.openfeign</groupId>
          <artifactId>feign-httpclient</artifactId>
      </dependency>
      
  • 2)配置连接池

    • 在order-service的application.yml中添加配置,默认是开启的,所以就算没有配置也会有一些默认的配置参数

    • feign:
        httpclient:
          enabled: true # 开启feign对HttpClient的支持
          max-connections: 200 # 最大的连接数
          max-connections-per-route: 50 # 每个路径的最大连接数
      
    • PS:每个不同的微服务连接路径是不同的,如访问user-service这个微服务就是一条路径,最多有50个连接;通常最大连接数除以微服务总数,即可得出每个微服务平均占用多少个连接。后期可以根据访问性能的变化来进行调整

  • 3)在FeignClientFactoryBean的loadBalance方法中打断点

    • debug方式启动order-service服务
    • 可以看到这里的client,未配置pom.xml前使用的是默认的Client
    • 配置后底层就是Apache HttpClient

2.3.2、小结

  • Feign-HttpClient的作用
    • 默认情况Feign发起的远程调用使用的是URLConnection,URLConnection是不支持连接池的,每次远程调用都需要花费大量的时间去建立连接,使用Feign-HttpClient可以对url连接可以存储到连接池中,提高效率

2.4、最佳实践

所谓最佳实践,就是使用过程中总结的经验,得出的一种最好的方式

  • 仔细观察不难发现,Feign客户端与服务提供者的Controller代码非常相似
    • Feign客户端
    • UserController
  • 思考一种方法简化这种重复的代码编写

2.4.1、继承方式

  • 一样的代码可以通过继承来共享
    • 1)定义一个API接口,利用定义方法,并基于SpringMVC注解声明
    • 2)Feign客户端和Controller都继承该接口

优点

  • 编码简单
  • 实现了代码共享

缺点

  • 服务提供方、服务消费方紧耦合
  • 参数列表中的注解映射并不会继承,因此Controller中必须再次声明方法、参数列表的注解

2.4.2、抽取方式

  • 将Feign的Client抽取为独立模块,并且把接口有关的POJO、默认的Feign配置都放到这个模块中,提供给所有消费者使用
  • 例如,将UserClient、User、Feign的默认配置都抽取到一个feign-api包中,所有微服务引用该依赖包,即可直接使用

①、抽取

  • 1)首先创建一个module,命名为feign-api,继承于cloud-demo父模块

  • 2)在feign-api中然后引入feign的starter依赖

    • <dependencies>
          <dependency>
              <groupId>org.springframework.cloud</groupId>
              <artifactId>spring-cloud-starter-openfeign</artifactId>
          </dependency>
      </dependencies>
      
  • 3)在order-service中编写的UserClient、User、DefaultFeignConfiguration都移动到feign-api项目中

  • 4)修改代码:UserClient中引入的User和包名DefaultFeignConfiguration不同了,其他类不变

    • 跟着idea操作即可,导包
  • 5)将feign-api安装到本地仓库(install)

②、使用feign-api

  • 1)首先,删除order-service中的UserClient、User、DefaultFeignConfiguration等类或接口

  • 2)在order-service的pom文件引入feign-api的依赖

    • <dependency>
          <groupId>cn.coolman.demo</groupId>
          <artifactId>feign-api</artifactId>
          <version>1.0</version>
      </dependency>
      

③、修改order-service

  • 1)修改Order
  • 2)修改Orderservice
  • 3)启动类中如果用到了DefaultFeignConfiguration也要改一下

这三步都是因为导包的问题,比较简单,这里就不演示了,跟着idea操作也能解决

④、重启测试

  • 重新运行程序后,发现服务报错了,报错信息如下所示
  • 这是因为UserClient现在在cn.coolman.feign.client包下,而order-service的@EnableFeignClients注解默认扫描的是cn.coolman.order包和它的子包,不在同一个父包中,无法扫描到UserClient

⑤、解决扫描包问题

  • 方式一

    • 指定Feign应该扫描的包:对整个包下的所有接口生成代理对象

    • @EnableFeignClients(basePackages = "cn.coolman.feign.client")
      
  • 方式二

    • 指定需要加载的Client接口:对指定的接口生成代理对象

    • @EnableFeignClients(clients = {UserClient.class})
      
  • 测试

有关07-Feign远程调用的更多相关文章

  1. 使用 ACL 调用 upload_file 时出现 Ruby S3 "Access Denied"错误 - 2

    我正在尝试编写一个将文件上传到AWS并公开该文件的Ruby脚本。我做了以下事情:s3=Aws::S3::Resource.new(credentials:Aws::Credentials.new(KEY,SECRET),region:'us-west-2')obj=s3.bucket('stg-db').object('key')obj.upload_file(filename)这似乎工作正常,除了该文件不是公开可用的,而且我无法获得它的公共(public)URL。但是当我登录到S3时,我可以正常查看我的文件。为了使其公开可用,我将最后一行更改为obj.upload_file(file

  2. c# - 如何在 ruby​​ 中调用 C# dll? - 2

    如何在ruby​​中调用C#dll? 最佳答案 我能想到几种可能性:为您的DLL编写(或找人编写)一个COM包装器,如果它还没有,则使用Ruby的WIN32OLE库来调用它;看看RubyCLR,其中一位作者是JohnLam,他继续在Microsoft从事IronRuby方面的工作。(估计不会再维护了,可能不支持.Net2.0以上的版本);正如其他地方已经提到的,看看使用IronRuby,如果这是您的技术选择。有一个主题是here.请注意,最后一篇文章实际上来自JohnLam(看起来像是2009年3月),他似乎很自在地断言RubyCL

  3. java - 从 JRuby 调用 Java 类的问题 - 2

    我正在尝试使用boilerpipe来自JRuby。我看过guide从JRuby调用Java,并成功地将它与另一个Java包一起使用,但无法弄清楚为什么同样的东西不能用于boilerpipe。我正在尝试基本上从JRuby中执行与此Java等效的操作:URLurl=newURL("http://www.example.com/some-location/index.html");Stringtext=ArticleExtractor.INSTANCE.getText(url);在JRuby中试过这个:require'java'url=java.net.URL.new("http://www

  4. ruby - 调用其他方法的 TDD 方法的正确方法 - 2

    我需要一些关于TDD概念的帮助。假设我有以下代码defexecute(command)casecommandwhen"c"create_new_characterwhen"i"display_inventoryendenddefcreate_new_character#dostufftocreatenewcharacterenddefdisplay_inventory#dostufftodisplayinventoryend现在我不确定要为什么编写单元测试。如果我为execute方法编写单元测试,那不是几乎涵盖了我对create_new_character和display_invent

  5. 【鸿蒙应用开发系列】- 获取系统设备信息以及版本API兼容调用方式 - 2

    在应用开发中,有时候我们需要获取系统的设备信息,用于数据上报和行为分析。那在鸿蒙系统中,我们应该怎么去获取设备的系统信息呢,比如说获取手机的系统版本号、手机的制造商、手机型号等数据。1、获取方式这里分为两种情况,一种是设备信息的获取,一种是系统信息的获取。1.1、获取设备信息获取设备信息,鸿蒙的SDK包为我们提供了DeviceInfo类,通过该类的一些静态方法,可以获取设备信息,DeviceInfo类的包路径为:ohos.system.DeviceInfo.具体的方法如下:ModifierandTypeMethodDescriptionstatic StringgetAbiList​()Obt

  6. C51单片机——实现用独立按键控制LED亮灭(调用函数篇) - 2

    说在前面这部分我本来是合为一篇来写的,因为目的是一样的,都是通过独立按键来控制LED闪灭本质上是起到开关的作用,即调用函数和中断函数。但是写一篇太累了,我还是决定分为两篇写,这篇是调用函数篇。在本篇中你主要看到这些东西!!!1.调用函数的方法(主要讲语法和格式)2.独立按键如何控制LED亮灭3.程序中的一些细节(软件消抖等)1.调用函数的方法思路还是比较清晰地,就是通过按下按键来控制LED闪灭,即每按下一次,LED取反一次。重要的是,把按键与LED联系在一起。我打算用K1来作为开关,看了一下开发板原理图,K1连接的是单片机的P31口,当按下K1时,P31是与GND相连的,也就是说,当我按下去时

  7. ruby - 如何找到调用当前方法的方法 - 2

    如何找到调用此方法的位置?defto_xml(options={})binding.pryoptions=options.to_hifoptions&&options.respond_to?(:to_h)serializable_hash(options).to_xml(options)end 最佳答案 键入caller。这将返回当前调用堆栈。文档:Kernel#caller.例子[0]%rspecspec10/16|===================================================62=====

  8. ruby-on-rails - 使用 HTTParty 的非常基本的 Rails 4.1 API 调用 - 2

    Rails相对较新。我正在尝试调用一个API,它应该向我返回一个唯一的URL。我的应用程序中捆绑了HTTParty。我已经创建了一个UniqueNumberController,并且我已经阅读了几个HTTParty指南,直到我想要什么,但也许我只是有点迷路,真的不知道该怎么做。基本上,我需要做的就是调用API,获取它返回的URL,然后将该URL插入到用户的数据库中。谁能给我指出正确的方向或与我分享一些代码? 最佳答案 假设API为JSON格式并返回如下数据:{"url":"http://example.com/unique-url"

  9. ruby - 为什么当我调用类的实例方法时,初始化不显示为方法? - 2

    我正在写一篇关于在Ruby中几乎一切都是对象的博客文章,我试图通过以下示例来展示这一点:classCoolBeansattr_accessor:beansdefinitialize@bean=[]enddefcount_beans@beans.countendend所以从类中我们可以看出它有4个方法(当然,除非我错了):它可以在创建新实例时初始化一个默认的空bean数组它可以计算它有多少个bean它可以读取它有多少个bean(通过attr_accessor)它可以向空数组写入(或添加)更多bean(也通过attr_accessor)但是,当我询问类本身它有哪些实例方法时,我没有看到默认

  10. ruby-on-rails - Rake 任务仅调用一次时执行两次 - 2

    我写了一个非常简单的rake任务来尝试找到这个问题的根源。namespace:foodotaskbar::environmentdoputs'RUNNING'endend当在控制台中执行rakefoo:bar时,输出为:RUNNINGRUNNING当我执行任何rake任务时会发生这种情况。有没有人遇到过这样的事情?编辑上面的rake任务就是写在那个.rake文件中的所有内容。这是当前正在使用的Rakefile。requireFile.expand_path('../config/application',__FILE__)OurApp::Application.load_tasks这里

随机推荐