Sping Cloud Feign 配置、使用和源码分析

Spring 专栏收录该内容
13 篇文章 0 订阅

官网文档:https://spring.io/projects/spring-cloud-openfeign

1. 简介

Spring Cloud OpenFeign : Declarative REST Client: Feign(音[feɪn]即"飞恩", 声明式 REST 服务调用)是一种声明式的 webService 客户端,可以使用它的注解修饰接口,它也支持自定义编解码。Spring Cloud 集成了 Ribbon 和 Eureka 为客户端提供了负载均衡策略

Feign是实现服务的远程调用技术。主要是作用在服务客户端,用于实现服务的调用。

Feign有两个主要注解: @EnableFeignClients 用于开启feign功能,@FeignClient 用于定义feign 接口

2. 基本使用

2.1 Feign 依赖

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

2.2 Feign 注解

接口:

@FeignClient(name = "yiguan", url = "${serverapi.yiguan.outer.url}")
public interface YiGuanFeign {
    /**
     * 获取用户信息
     *
     * @param params 请求参数
     * @return 返回结果
     */
    @PostMapping("${serverapi.yiguan.outer.accessdataapi}")
    Object getAccessDataApi(@RequestBody Map<String, Object> params);
}

启动类:

@SpringBootApplication
@EnableFeignClients
public class MpServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(MpServiceApplication.class, args);
    }
}

2.3 Feign 测试

调用外部易观的PV和UV访问数据测试(数据已解析):

image-20210307175426921

请求和响应(日志):

image-20210307175816223

2.4 Feign 配置(可选)

  • Feign 配置自定义连接超时时间、读取响应超时时间
#Feign 连接超时时间和读取响应超时时间配置
feign:
  client:
    config:
      default:
        connectTimeout: 5000    #连接超时时间
        readTimeout: 5000       #读取超时时间
        loggerLevel: basic      #日志等级
  • 使用 Okhttp 发送 request

    Okhttp优势:
    网络优化方面
    (1)内置连接池,支持连接复用;
    (2)支持gzip压缩响应体;
    (3)通过缓存避免重复的请求;
    (4)支持http2,对一台机器的所有请求共享同一个socket。

    功能方面
    功能全面,满足了网络请求的大部分需求

    扩展性方面:
    责任链模式使得很容易添加一个自定义拦截器对请求和返回结果进行处理

feign:
  client:
    config:
      default:
        connectTimeout: 5000
        readTimeout: 5000
        loggerLevel: basic
  okhttp:
    enabled: true  #使用OKhttp发送request
  hystrix:
    enabled: true
  • Spring Cloud Feign支持对请求和响应进行 GZIP 压缩,以提高通信效率:
feign:
  compression:
    request:  #请求
      enabled: true  #开启
      mime-types: text/xml,application/xml,application/json  #开启支持压缩的MIME TYPE
      min-request-size: 2048  #配置压缩数据大小的下限
    response:  #响应
      enabled: true  #开启响应GZIP压缩

3. 源码分析

疑问:

  • 请求是如何转到 Feign 的 ?

  • Feign 是怎么工作的 ?

  • Feign 的负载均衡策略?

通过源码分析解答这三个疑问。

3.1 原理和源码详解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {

	/**
	 * value:basePackages的别名
	 */
	String[] value() default {};

	/**
	 * basePackages:要扫描的包的路径
	 */
	String[] basePackages() default {};

	/**
	 * basePackageClasses:basePackages 的类型安全替代属性,用于指定要扫描的组件以扫描带该注解的组件。
	 */
	Class<?>[] basePackageClasses() default {};

	/**
	 * defaultConfiguration:用于覆盖 FeignClient 的默认配置和默认 Bean 定义
	 */
	Class<?>[] defaultConfiguration() default {};

	/**
	 * clients:用@FeignClient注解的类的列表。如果不为空,则禁用类路径扫描。
	 */
	Class<?>[] clients() default {};
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FeignClient {

    /**
     * name/value:指定FeignClient的名称,如果项目使用了Ribbon,name属性会作为微服务的名称,用于服务发现
     */
    @AliasFor("name")
    String value() default "";

    /**
     * serviceId:serviceId 已经废弃了,直接使用 name 即可
     */
    @Deprecated
    String serviceId() default "";

    /**
     * contextId:Bean 名称冲突后的解决方案,如果配置了 contextId 就会用 contextId,
     * 如果没有配置就会去 value 然后是 name 最后是 serviceId,
     * 默认都没有配置,当出现一个服务有多个 Feign Client 的时候就会报错了。
     * contextId 会作为 Client 别名的一部分,如果配置了 qualifier 优先用 qualifier 作为别名。
     */
    String contextId() default "";

    /**
     * name/value:指定FeignClient的名称,如果项目使用了Ribbon,name属性会作为微服务的名称,用于服务发现
     */
    @AliasFor("value")
    String name() default "";

    /**
     * qualifier:对应的是@Qualifier 注解,一般场景直接@Autowired 直接注入就可以了
     * 如果 Feign Client 有 fallback 实现,默认@FeignClient 注解的 primary=true, 
     * 意味着 @Autowired 注入是没有问题的,会优先注入 Feign Client。
     * 如果把 primary 设置成 false 了,直接用 @Autowired 注入的地方就会报错,
     * 不知道要注入哪个对象。
     * 解决方案:
     * 将 primary 设置成 true 即可,如果由于某些特殊原因,必须得去掉 primary=true 的设置,
     * 这种情况下可以配置一个 qualifier,使用 @Qualifier 注解进行注入
     */
    String qualifier() default "";

    /**
     * url: 用于配置指定服务的地址,相当于直接请求这个服务,不经过 Ribbon 的服务选择。像调试等场景可以使用。
     */
    String url() default "";

    /**
     * decode404:当发生http 404错误时,如果该字段位true,会调用decoder进行解码,否则抛出FeignException
     */
    boolean decode404() default false;

    /**
     * configuration: Feign配置类,可以自定义Feign的Encoder、Decoder、LogLevel、Contract
     */
    Class<?>[] configuration() default {};

    /**
     * fallback: 定义容错的处理类,当调用远程接口失败或超时时,会调用对应接口的容错逻辑,
     * fallback指定的类必须实现@FeignClient标记的接口,无法知道熔断的异常信息。
     */
    Class<?> fallback() default void.class;

    /**
     * fallbackFactory: 工厂类,用于生成fallback类示例,通过这个属性我们可以实现每个接口通用的容错逻辑,
     * 减少重复的代码,可以知道熔断的异常信息。
     */
    Class<?> fallbackFactory() default void.class;

    /**
     * path: 定义当前 FeignClient 访问接口时的统一前缀,比如接口地址是/user/get, 如果你定义了前缀是 user, 
     * 那么具体方法上的路径就只需要写/get 即可。
     */
    String path() default "";

    /**
     * 对应的是@Primary 注解,默认为 true,官方这样设置也是有原因的。当我们的 Feign 实现了 fallback 后,
     * 也就意味着 Feign Client 有多个相同的 Bean 在 Spring 容器中,当我们在使用@Autowired 进行注入的时候,
     * 不知道注入哪个,所以我们需要设置一个优先级高的,@Primary 注解就是干这件事情的。
     */
    boolean primary() default true;
}
  1. Feign基本原理
  • 启动时,程序会进行包扫描,扫描所有包下所有 @FeignClient 注解的类,并将这些类注入到 spring 的 IOC 容器中。当定义的 Feign 中的接口被调用时,通过 JDK 的动态代理来生成 RequestTemplate。
  • RequestTemplate 中包含请求的所有信息,如请求参数,请求URL等。
  • RequestTemplate 生成 Request,然后将 Request 交给 client 处理,这个 client 默认是 JDK 的 HTTPUrlConnection ,也可以是 OKhttp、Apache 的 HTTPClient 等。
  • 最后 client 封装成 LoadBaLanceClient,结合 Ribbon 负载均衡地发起调用。
  1. Feign源码分析(图解)

image-20210307174113861

image-20210307174301648

3.2 请求是如何转到 Feign 的?

分为两部分,第一是为接口定义的每个接口都生成一个实现方法,结果就是 SynchronousMethodHandler 对象。第二是为该服务接口生成了动态代理。动态代理的实现是 ReflectiveFeign.FeignInvocationHanlder,代理被调用的时候,会根据当前调用的方法,转到对应的 SynchronousMethodHandler。

3.3 Feign 是怎么工作的?

当对接口的实例进行请求时(Autowire 的对象是某个ReflectiveFeign.FeignInvocationHanlder 的实例),根据方法名进入了某个 SynchronousMethodHandler 对象的 invoke 方法。

SynchronousMethodHandler 其实也并不处理具体的 HTTP 请求,它关心的更多的是请求结果的处理。HTTP 请求的过程,包括服务发现,都交给了当前 context 注册中的 Client 实现类,比如 LoadBalancerFeignClient。Retry 的逻辑实际上已经提出来了,但是 fallback 并没有在上面体现,因为我们上面分析动态代理的过程中,用的是 Feign.Builder,而如果有 fallback 的情况下,会使用 HystrixFeign.Builder,这是 Feign.Builder 的一个子类。它在创建动态代理的时候,主要改了一个一个东西,就是 invocationFactory 从默认的 InvocationHandlerFactory.Default 变成了一个内部匿名工厂,这个工厂的create 方法返回的不是 ReflectiveFeign.FeignInvocationHandler,而是 HystrixInvocationHandler。所以动态代理类换掉了,invoke 的逻辑就变了。在新的逻辑里,没有简单的将方法转到对应的 SynchronousMethodHandler 上面,而是将 fallback 和 SynchronousMethodHandler一起封装成了 HystrixMethod,并且执行该对象。

3.4 Feign 的负载均衡策略?

Feign 默认集成了 Ribbon 的轮询方式的负载均衡策略。

Feign 的时候,如何去切换到 Ribbon 中其他均衡策略呢?甚至切换到自定义的策略呢?

在 application.yml 配置文件中来指定,如下:

# feign和ribbon结合,指定负载均衡策略为【随机策略】
MICROSERVICE-ORDER:
 ribbon:
   NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

MICROSERVICE-ORDER 表示作用到哪个微服务,com.netflix.loadbalancer.RandomRule 即 Ribbon 里面的随机策略,当然,也可以指定为其他策略,包括自己定义的,只要把相应的包路径写到这即可。

  • 0
    点赞
  • 0
    评论
  • 1
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

相关推荐
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值