SpringCloud——Feign知识整理

目录

1. Feign的基本使用

第一步:引入Feign的依赖,这里要注意,当模块A要调用模块B的方法时,要在调用者中加入Feign的依赖,依赖如下

第二步:要在调用者的启动类上添加注解

第三步:编写Feign的客户端,假设,在orderservice层,查询订单信息,需要调用userservice层查询用户信息的方法

2. 自定义Feign的配置

3.  Feign的性能优化

4.  Feign的最佳实践,如何使用Feign? 

第一种(继承):给消费者的FeignCilent和提供者的Controller定义统一的父接口作为标准,如下图所示:

第二种(抽取):将FeignClient抽取成一个独立模块,并且把有关的POJO,默认的Feign配置都可以放在这个模块当中,提供给所有消费者使用。这样说可能有些抽象,我来简单举例说明

5.  最佳实践实际操作(方式二)

(1)这里我们创建feign-api模块,

(2)定义UserClient

方案一:在启动类@EnableFeignClients注解上指定要扫描的client包

方案二:仍然是在启动类上的@EnableFeignClients注解上指定要扫描的特定的字节码文件,也就是你用哪个扫哪个,如下所示:


1. Feign的基本使用

Feign是一个声明式的http客户端,我们知道,在不使用Feign之前,在微服务中,一个模块如果想要调用另一个模块中的某个功能,需要向其发起请求http请求,如果不使用Feign,我们就需要通过硬编码的形式去编写构建http请求,例如使用RestTemplate发起远程调用。如下所示:

可以看到,这样的编码明显是不太友好的。

代码可读性差,而且编程体验也不好;

参数复杂时URL难以维护。

因此,就要引出我们的Feign,Feign可以帮助我们发送http请求,解决上面的问题。

那么Feign该怎样使用呢?其实很简单

第一步:引入Feign的依赖,这里要注意,当模块A要调用模块B的方法时,要在调用者中加入Feign的依赖,依赖如下
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
第二步:要在调用者的启动类上添加注解
@EnableFeignClients

开启Feign功能,不加注解是不启动的

第三步:编写Feign的客户端,假设,在orderservice层,查询订单信息,需要调用userservice层查询用户信息的方法

 

 在orderservice模块中新建一个包client,需要声明的客户端如下,其实就是一个接,因为是要查询User信息,所以定义为UserClient

@FeignClient(value = "userservice")
public interface UserClient {

    @GetMapping("/user/{id}")
    User findById(@PathVariable("id") Long id);
}

这里声明的客户端需要和上述中的方法体保持一致,都要使用@GetMapping,路径也要保持一致。@FeignClient(value = "userservice")这里面因为我写的这个方法是userservice业务模块的,所以指定value值就是userservice。

完成上述三步之后,在orderservice业务模块中查询订单的方法中,我们就不需要像之前那样采用硬编码的形式,采用自动装配userClient,那么方法及可以改成下面这种形式

(1) 用户传入要查询的订单信息,通过orderId查询到订单

(2)然后我们直接就可以使用注入的userClient远程调用,其实就是调用了我们定义的接口

此外这里多提一句,Feign不仅可以帮助我们发送请求,还可以帮助我们实现负载均衡,因为在Feign内部,已经集成了Ribbon。

2. 自定义Feign的配置

Spring Boot已经自动装配了Feign的配置,但是我们自己是可以修改或者自定义Feign的配置的,可以修改的配置如下:

 上面是我们最常用到的,当然也还有其他的,这里就不说了

简单来说一下日志,日志一共分为四个级别

NONE:没有任何日志,也是默认配置;

BASIC:可以记录http请求什么时候发送,什么时候结束,耗时多久等基本信息;

HEADERS:除了BASIC中含有的以外,还包含请求头和响应头; 

FULL:是最全面的,除了HEADERS中包含的以外,还包含请求体信息和响应体信息。

自定义Feign配置是有两种方式的

方式一:配置文件方式,这里又可分为全局生效和局部生效

在application.yml文件中的全局配置如下:

 其实default默认的就是全局配置,如果想要局部生效(即对某个微服务生效,将default改成对应的服务名称即可)。

这里loggerLevel我定义为FULL,我先清空一下控制台

然后打开浏览器查询一下102订单,查询成功如下

再来看控制台打印的信息

可以看到打印了一大堆日志。

 方式二:就是采用Java代码的形式,需要先申明一个Bean,在Bean中定义好日志的等级

public class DefaultFeignConfiguration {
    @Bean
    public Logger.Level logLevel(){
        return Logger.Level.BASIC;
    }
}

当然了,这里也分为全局生效和局部生效

想要局部生效时,就在Feign的具体的接口上的注解上添加configuration = FeignConfiguration.class,如下所示:

@FeignClient(value = "userservice",configuration = DefaultFeignConfiguration.class)
public interface UserClient {

    @GetMapping("/user/{id}")
    User findById(@PathVariable("id") Long id);
}

想要全局生效时,就在功能模块的启动类上的启动Feign的注解上添加值defaultConfiguration = FeignConfiguration.class,如下所示:

@EnableFeignClients(defaultConfiguration = DefaultFeignConfiguration.class)

这里我也设置为全局生效,重启服务器,打开浏览器仍然查询订单102,打开idea重新观察,如下所示

 可以看到BASIC相比于FULL,明显少了许多。

总结得出如下图

 这里我建议,如果是调试错误的时候,可以使用FULL;但如果日常开发,建议使用NONE或BASIC。因为记录日志也是会消耗一定的性能的。

3.  Feign的性能优化

说起性能调优,大家都不陌生,不管开发什么,用什么工具,都会有调优这一说,例如jvm调优,mysql调优。其实Feign的性能本身已经很好了,但是还是有优化的余地,我们先来了解一下Feign的底层实现

 第一种URLConnection是默认实现,是JDK自带的,它的性能是不太好的,而且不支持连接池。

连接池可以避免我们创建和销毁的一个性能损耗,而且连接需要三次握手,断开需要四次挥手,是比较浪费性能的。所以我们可以使用另外两个,所以对Feign性能的优化,就是对底层实现的改变。还有就是我们上述的日志的级别,也是对Feign性能稍微有影响的,这也是为什么Feign日志默认是NONE。

这里我就采用Apache HttpClient来实现

第一步:引入HttpClient的依赖,maven依赖如下

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

第二步:配置连接池

feign:
  httpclient:
    enabled: true # 支持HttpClient的开关
    max-connections: 200 # 最大连接数
    max-connections-per-route: 50 # 单个路径的最大连接数

这里enable要设置为true,打开开关;下边的连接参数和我们之前学的数据库连接池有些相似,很好理解,这两个数量设置多少合适没有具体数值,可以在具体的业务中进行测试,测试设置为多少位最好,因项目而异。

总结如下图

4.  Feign的最佳实践,如何使用Feign? 

关于Feign如何使用,大概可以分为以下两种:

第一种(继承):给消费者的FeignCilent和提供者的Controller定义统一的父接口作为标准,如下图所示:

 因为orderservice业务中要向userservice发起请求,要通过FeignCilent,FeignClient又需要定义和userservice业务中控制层完全一样的接口,所以我们可以定义一个父接口,让UserClient接口去继承这个父接口,让userservice业务中Controller层去实现这个父接口,之前我们的Controller层都是自己定义方法,这样一来就不需要了,我们直接实现父接口,UserAPI也算是一种规范,算是对子类型为的一个约束,给Feign客户端和Controller定义统一的标准。

但是,这种方式确有一定的问题,下面这段话是Spring官方给出的一个说明

翻译过来就是,“一般情况下不推荐在服务端和客户端共享一个接口,因为它会造成紧耦合,而且这种继承方案对SpringMVC是不起作用的”。我来简单解释几个问题,

第一,紧耦合,因为我们的服务端FeignClient和客户端Controller都实现了同一个接口,一旦后期我们的接口发生了改变,那么两端都需要发生改变,需要付出巨大的成本。我们做项目都希望高内聚低耦合,方便后期对业务的扩展,所以这个方法是有点缺点的。

第二:“这种继承方案对SpringMVC是不起作用的”这句话的意思也就是说,我们在Controller层继承此接口时,参数是不能继承下来的,例如我们所写的@PathVariable,这个是需要我们自己补充的。

 尽管上述两点是此方法的一些缺点,但是!!!这个方法实现了面向契约编程,而且在一些小型微服务项目中特别合适,因此仍然也是比较流行的。

第二种(抽取):将FeignClient抽取成一个独立模块,并且把有关的POJO,默认的Feign配置都可以放在这个模块当中,提供给所有消费者使用。这样说可能有些抽象,我来简单举例说明

如下:

有三个模块,user-service,orde-rservice,pay-service,假设我们的orde-rservice,pay-service模块都需要user-service模块中的功能,按照我们的第一种做法,就是各自都实现UserClient,如果后期模块越来越多,相互调用越来越复杂,那么这个项目的耦合度是非常恐怖的,就背离了我们的微服务项目的初衷。

由此就引出了第二种方法

 

 这里我们把Feign也当作微服务的一个模块,专门用来存放各种的POJO类和默认配置,这样的话,我们的所有业务模块只需要引入我们的feign-api依赖即可直接使用。这样我们就大大的降低了项目的耦合度。

但是!!!这种方案也有一定的问题。例如,我的order-service模块只需要feign-api模块中的一个功能,但是我又必须要用,我就必须将feign-api所有的东西都通过依赖拿过来,不管用不用,其他模块也是如此,这样就造成了一定资源的浪费。

综上所述,两种方案都是有利有弊。看项目和公司如何选择了,如果不想耦合,就可以选方式二;如果不很在意耦合想省事,就可以使用方式一。没有完美的解决方案,根据需要选择即可。

5.  最佳实践实际操作(方式二)

刚才我们说了继承和抽取两种方案,这里的话我实际操作一下,我选择的是方式二(抽取)。

步骤分为三步:

(1)新建feign-api模块,引入feign的依赖;

(2)将所需要的Client定义到feign-api模块中;

(3)在所需要的业务模块中引入我们创建的feign-api依赖;

(4)完成上述操作之后,我们都可以重启测试了。

(1)这里我们创建feign-api模块,

 gateway模块不需要关心,与本次无关。

打开pom文件继承父工程cloud-demo。引入openfeign依赖,如下

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>cloud-demo</artifactId>
        <groupId>cn.itcast.demo</groupId>
        <version>1.0</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>feign-api</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
    </dependencies>
</project>
(2)定义UserClient

这里我只是做举例,所以实体类只写了User,之前orderservice业务中的User累就可以删掉了。

@Data
public class User {
    private Long id;
    private String username;
    private String address;
}

实际项目中用到那个写那个。

这里我只定义了关于User的Client接口,实际开发也一样,有别的业务在定义别的业务的接口

@FeignClient(value = "userservice")
public interface UserClient {

    @GetMapping("/user/{id}")
    User findById(@PathVariable("id") Long id);
}

这里我单独创建config包定义了日志的等级

public class DefaultFeignConfiguration {
    @Bean
    public Logger.Level logLevel(){
        return Logger.Level.BASIC;
    }
}

和上面第二节我说的一样,全局生效就在对应模块的启动类上配置,局部生效就在上面定义的UserClient接口上的@FeignClient中配置,这里我是全局生效就不再展示了。

(3)在对应的业务模块中引入我们定义的feign-api依赖,这里就是orderservice业务模块,这里我直接把orderservice的pom文件全展示一下吧

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>cloud-demo</artifactId>
        <groupId>cn.itcast.demo</groupId>
        <version>1.0</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>order-service</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <!--mybatis-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
        </dependency>
        <!-- nacos客户端依赖包 -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!--feign客户端依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <!--优化feign引入依赖-->
        <dependency>
            <groupId>io.github.openfeign</groupId>
            <artifactId>feign-httpclient</artifactId>
        </dependency>
        <!--引入我们的feign-api依赖-->
        <dependency>
            <groupId>cn.itcast.demo</groupId>
            <artifactId>feign-api</artifactId>
            <version>1.0</version>
        </dependency>
    </dependencies>
    <build>
        <finalName>app</finalName>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

这里还有一点非常重要,千万千万要注意,当我们将feign独立成一个模块之后,我们的Spring容器再去注入Client时,是会报错的。因为我们在独立出来之前,orderservice模块启动类默认扫描的是整个orderservice模块,当时我们把feign写在orderservice模块中,肯定是能扫描到的。但现在独立出来了,它就扫不到了,会报错。

这里有两种解决方案

方案一:在启动类@EnableFeignClients注解上指定要扫描的client包

如下所示:

方案二:仍然是在启动类上的@EnableFeignClients注解上指定要扫描的特定的字节码文件,也就是你用哪个扫哪个,如下所示:

如果你某个模块有多个client要用时,可以扫包,更快捷;

如果你某个模块只有一个client要用是,可以扫指定的文件。

两种方式都可以,根据需要即可

(4)完成上述操作之后我们重启项目,在浏览器输入localhost:8088/order/102 查询订单id为102的订单信息如下所示

 可以看到我们已经查询成功了。所以项目应该是没有问题的。

  • 21
    点赞
  • 53
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值