• SpringCloud——Http客户端Feign


    五、http客户端Feign

    5.1 初识Feign

    前面我们使用远程调用一直用的都是RestTemplate对象来发送请求,源码如下:

    String url = "http://user/user/"+order.getUserId();
    User user = restTemplate.getForObject(url, User.class);
    
    • 1
    • 2

    这种写法,有两个比较明显的缺点啊。

    1、可读性差

    2、维护困难

    为了解决这种不优雅的写法,我们可以使用Feign来代替我们的远程调用。那么Feign是什么东西呢?

    Feign是一个声明式的http客户端,他能很好地帮助我们实现http请求的发送,解决使用RestTemplate带来的两个弊端。官网地址:https://github.com/OpenFeign/feign,或者也可以使用该镜像链接,速度会快一些。

    废话少说,接下来我们就来说一下怎么用我们的Feign完美替代我们的RestTemplate,实现更优雅的代码编写。对Feign的简单使用,分为以下四个步骤:

    1. 导入Feign依赖
    2. 给启动类添加**@EnableFeignClients**注解
    3. 编写Feign客户端(写接口)
    4. 发送请求(实现远程调用)

    首先,第一步我们肯定是导入Feign的依赖啦。(在我们的order服务中导入)

    <dependency>
        <groupId>org.springframework.cloudgroupId>
        <artifactId>spring-cloud-starter-openfeignartifactId>
    dependency>
    
    • 1
    • 2
    • 3
    • 4

    第二步,我们在需要做远程调用的服务的启动类上方添加**@EnableFeignClients注解。(OrderApplication的类上方添加@EnableFeignClients**注解)

    @EnableFeignClients
    @SpringBootApplication
    @MapperScan("com.example.order.mapper")
    public class OrderApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(OrderApplication.class, args);
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    第三步,编写Feign的客户端。这个客户端主要是基于SpringMVC的注解来声明远程调用的信息,比如:

    • 服务名称:user
    • 请求方式:GET
    • 请求路径:/user/{id}
    • 请求参数:Long id
    • 返回值类型:User

    这样,Feign就可以帮助我们发送http请求,无需自己使用RestTemplate来发送了。

    package com.example.order.client;
    
    import com.example.order.po.User;
    import org.springframework.cloud.openfeign.FeignClient;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    
    //请求的服务名
    @FeignClient("user")
    public interface UserClient {
        //Get请求,路径为/user/{id}
        @GetMapping("/user/{id}")
        User findById(@PathVariable("id") Long id);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    第四步,发送请求。此时,只需要将我们的远程调用代码修改为:

    package com.example.order.service;
    
    import com.example.order.client.UserClient;
    import com.example.order.mapper.OrderMapper;
    import com.example.order.po.Order;
    import com.example.order.po.User;
    import org.springframework.stereotype.Service;
    import org.springframework.web.client.RestTemplate;
    
    import javax.annotation.Resource;
    
    @Service
    public class OrderService {
        @Resource
        OrderMapper orderMapper;
    
        @Resource
        RestTemplate restTemplate;
    
        @Resource
        UserClient userClient;
    
        public Order selectById(Long id) {
            //1、获取订单信息
            Order order = orderMapper.selectById(id);
            //2、获取http请求结果
            User user = userClient.findById(order.getUserId());
            //3、封装到order
            order.setUser(user);
            return order;
    //        //1、获取订单信息
    //        Order order = orderMapper.selectById(id);
    //        //2、利用RestTemplate获取http请求
    //        String url = "http://user/user/"+order.getUserId();
    //        /**
    //         *  getForObject方法
    //         *  第一个参数是请求的url
    //         *  第二个参数是预封装的实体类
    //         */
    //        User user = restTemplate.getForObject(url, User.class);
    //        //3、封装到order
    //        order.setUser(user);
    //        return order;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    重启服务,访问 http://localhost:8080/order/102 运行结果如下:
    在这里插入图片描述

    5.2 自定义Feign的配置

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

    类型作用说明
    feign.Logger.Level修改日志级别包含四种不同的级别:NONE、BASIC、HEADERS、FULL
    feign.codec.Decoder响应结果的解析器http远程调用的结果做解析,例如解析json字符串为java对象
    feign.codec.Encoder请求参数编码将请求参数编码,便于通过http请求发送
    feign. Contract支持的注解格式默认是SpringMVC的注解
    feign. Retryer失败重试机制请求失败的重试机制,默认是没有,不过会使用Ribbon的重试

    注意:一般情况下,默认值就能满足我们使用,如果要自定义时,只需要创建自定义的@Bean覆盖默认Bean即可。

    一般我们需要配置的其实也就是日志级别,而日志的这四种级别分别会输出的信息也各有不同:

    • NONE:不记录任何日志信息,这是默认值。
    • BASIC:仅记录请求的方法,URL以及响应状态码和执行时间
    • HEADERS:在BASIC的基础上,额外记录了请求和响应的头信息
    • FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据。

    下面以修改Feign的日志级别为例,默认情况下Feign的日志级别为NONE,这里,我们直接实战将其修改为BASIC。

    方法一、在配置文件中进行配置

    配置信息如下:

    情景一、假设我们希望所有的服务都采用BASIC日志级别,那么配置信息如下:

    feign:
      client:
        config:
          #default表示修改所有的服务的配置信息
          default:
            loggerLevel: BASIC
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    此时发送请求,结果如下,多了两行数据,记录了请求的方法,URL以及响应状态码和执行时间:
    在这里插入图片描述
    情景二、假设我们只针对某个服务要修改其日志级别为BASIC,那么配置信息如下:

    feign:
      client:
        config:
          #user表示只针对远程user服务时候的配置
          user:
            loggerLevel: BASIC
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    方法二、基于Java源码进行配置

    如果是学习的话,记得先将yaml文件中对日志级别的配置信息先注释掉。

    基于Java源码进行配置,我们需要写一个配置类如下:

    package com.example.order.config;
    
    import feign.Logger;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    
    @Configuration
    public class FeignConfig {
        @Bean
        public Logger.Level feignLogLevel(){
            //设置日志级别为HEADERS
            return Logger.Level.HEADERS;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    情景一、假设我们希望所有的服务都采用HEADERS日志级别

    那么我们需要修改我们启动类中,@EnableFeignClients注解如下:

    @EnableFeignClients(defaultConfiguration = FeignConfig.class)
    @SpringBootApplication
    @MapperScan("com.example.order.mapper")
    public class OrderApplication {
        public static void main(String[] args) {
            SpringApplication.run(OrderApplication.class, args);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    情景二、假设我们只针对某个服务要修改其日志级别为HEADERS

    那么我们需要修改我们的Feign客户端接口中,@FeignClient注解如下:(多一个value属性指定针对的服务)

    @FeignClient(value = "user", configuration = FeignConfig.class)
    public interface UserClient {
        @GetMapping("/user/{id}")
        User findById(@PathVariable("id") Long id);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    5.3 Feign的性能优化

    Feign作为声明式的http客户端,底层发起http请求,依赖于其它的框架。其底层客户端实现包括:

    • URLConnection: 不支持连接池(部分版本的Feign默认实现)

    • Apache HttpClient :支持连接池(部分版本的Feign默认实现)

    • OKHttp:支持连接池

    要想看自己的Feign默认实现的是那一个框架,我们可以在FeignClientFactoryBean文件中的loadBalance打断点。
    在这里插入图片描述
    然后查看获取到的client对象的delegate属性对应的类名。(我的Feign客户端默认使用的是ApacheHttpClient)
    在这里插入图片描述
    而对于部分默认使用URLConnection的Feign客户端,由于其不支持连接池,在性能上说实话还是有点不太好的,毕竟频繁地触发三次握手和四次挥手也是需要消耗资源的。所以,我们在性能的优化上就可以从这里下手,使用支持连接池的底层客户端实现代替我们 JDK 的 URLConnection。

    这里以Spring底层的Apache HttpClient为例,只需要以下两个步骤即可:

    步骤一、导入feign-httpclient依赖

    
    <dependency>
        <groupId>io.github.openfeigngroupId>
        <artifactId>feign-httpclientartifactId>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    步骤二、配置文件配置Feign

    feign:
      client:
        config:
          default:
            loggerLevel: BASIC  # 日志级别,BASIC就是基本的请求和响应信息
      httpclient:
        enabled: true           #开启feign对httpclient的支持
        max-connections: 200    #最大的连接数
        max-connections-per-route: 50 #每个路径的最大连接数
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    此时,在FeignClientFactoryBean文件中的loadBalance打断点
    在这里插入图片描述
    然后运行我们的user和order服务,发送请求后我们可以看到,client对象他的delegate属性对应的类名是ApacheHttpClient。
    在这里插入图片描述

    5.4 Feign的最佳实践

    方法一、继承,抽离远程调用的方法成一个接口(interface),让消费者的FeignClient继承该接口,并让提供者的Controller实现该接口。
    在这里插入图片描述
    这种做法的优点是:

    1、简单

    2、实现了代码共享。

    缺点:(Spring不建议使用这种写法的原因)

    1、高藕合

    2、参数列表中的注解映射并不会继承,因此Controller中必须再次声明方法、参数列表、注解

    方法二、抽取,将FeignClient抽取为独立的模块,并且把接口有关的POJO、默认的Feign配置都放到这个模块中,提供给消费者使用。
    在这里插入图片描述
    具体的代码实现请看:

    步骤一、创建一个module,命名为feign-api。
    在这里插入图片描述
    步骤二、在feign-api中引入openfeign场景的jar包。

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-starter-openfeignartifactId>
        dependency>
    dependencies>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    步骤三、编写我们请求需要用到的实体类(例如:User),编写我们Feign的Client接口(例如:UserClient)。可能还需要Feign的配置文件(前面写过的FeignConfig)。

    注意:这里,我们只需要把之前Order服务下的User.java,UserClient.java还有FeignConfig.java直接拿到feign-api这个module下就可以了。 包结构大致如下:(由于这里我写的包结构和order服务中的包结构有差别,所以,复制过来后,可能还得修改一下UserClient类导入其他类时候的包名)
    在这里插入图片描述
    消费者服务中引入feign-api的jar包。

    <dependency>
        <groupId>org.examplegroupId>
        <artifactId>feign-apiartifactId>
        <version>1.0-SNAPSHOTversion>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    针对本项目,我们首先得要把Order服务下导入的UserClient还有User类,改成导入com.example.api下的类(feign-api这个module的类),完事后还得删除order服务中的UserClient、User、FeignConfig这几个类或接口

    步骤五、在启动文件Application.java指定扫描包的路径。

    方法一、扫描feign-api下的client包下所有的类。

    @EnableFeignClients(basePackages = "com.example.api.client")
    
    • 1

    方法二、扫描feign-api下所需要的client类。

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

    然后我们重启order服务,访问localhost:8080/order/103结果如下:
    在这里插入图片描述

  • 相关阅读:
    【STM32】STM32学习笔记-WDG看门狗(46)
    ENSP实验-实现不同VLAN间互访的两种方式
    和monkey的相处日记
    STM32平替GD32有多方便
    攻防世界MISC练习区(gif 掀桌子 ext3 )
    Unity3D XML与Properties配置文件读取详解
    虚拟机开机字体变大,进入系统后字体模糊
    C# 设计原则 之 开放封闭 原则
    【JavaSE】继承那些事儿
    一些可以参考的文档集合10
  • 原文地址:https://blog.csdn.net/weixin_44741023/article/details/126088792