Netflix Feign - Spring Cloud 整合 Feign 原理

290 阅读5分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。


Feign组件

Feign的核心组件

  1. Encoder和Decoder:Encoder编码器,如果调用接口的时候,传递的参数是个对象,Feign需要将对象进行encode序列化成JSON;Decoder解码器,处理收到的JSON,转换成对象。
  2. Logger:打印日志。
  3. Contract:Feign的@FeignClient注解和Spring Web Mvc支持的@PathVariable、@RequestMapping、@RequestParam等注解结合起来使用。Feign不支持Spring Web Mvc的注解,Contract组件负责解释Spring Web Mvc的注解,使Feign可以跟第三方的注解结合起来使用。
  4. Feign.Builder:FeignClient的一个实例构造器。
  5. FeignClient:Feign入口。FeignClient,里面包含了一系列的组件,比如说Encoder、Decoder、Logger、Contract等。

Spring Cloud环境下Feign的默认组件

Decoder:ResponseEntityDecoder

Encoder:SpringEncoder

Logger:Slf4jLogger

Contract:SpringMvcContract

Feign.Builder:HystrixFeign.Builder

FeignClient:LoadBalancerFeignClient。底层整合Ribbon

自定义Feign组件

package center.leon.eurekaconsumerribbonfeignapi.product.config;

import feign.Request;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author : Leon on XXM Mac
 * @since : create in 2022/7/10 3:43 PM
 */
@Slf4j
@Configuration
public class ProductFacadeServiceFeignConfig {

    @Bean
    public RequestInterceptor requestInterceptor() {
        return requestTemplate -> {
            String url = requestTemplate.url();
            log.info("feign requestInterceptor log url : {}", url);
        };
    }
}

Feign的拦截器可以实现对Feign的请求进行拦截。实现RequestInterceptor,然后所有的请求发送之前都会被RequestInterceptor拦截。

Spring Cloud整合Feign自定义参数配置

普通参数配置
压缩配置
日志配置

普通参数配置

# feign service config
## feign eureka-provider-ribbon-feign-api-impl service config
feign.client.config.eureka-provider-ribbon-feign-api-impl.connect-timeout=5000
feign.client.config.eureka-provider-ribbon-feign-api-impl.read-timeout=5000
feign.client.config.eureka-provider-ribbon-feign-api-impl.logger-level=full
feign.client.config.eureka-provider-ribbon-feign-api-impl.decode404=false

## feign default service config
feign.client.config.default.connect-timeout=3000
feign.client.config.default.read-timeout=3000
feign.client.config.default.logger-level=headers
feign.client.config.default.decode404=true

压缩配置

# feign compression config
feign.compression.request.enabled=true
feign.compression.request.mime-types=text/xml,application/xml,application/json
feign.compression.request.min-request-size=2048
feign.compression.response.enabled=true

日志配置

logger有四种类型:NONE,BASIC,HEADERS, FULL

# feign log config
logging.level.center.leon.eurekaconsumerribbonfeignapi.product.server.ProductFacadeServiceFeign=debug
@Bean
public Logger.Level logger() {
    return Logger.Level.FULL;
}
2022-07-10 16:36:09.595 DEBUG 18741 --- [nio-9090-exec-1] c.l.e.p.s.ProductFacadeServiceFeign      : [ProductFacadeServiceFeign#listProduct] <--- HTTP/1.1 200 (218ms)
2022-07-10 16:36:09.595 DEBUG 18741 --- [nio-9090-exec-1] c.l.e.p.s.ProductFacadeServiceFeign      : [ProductFacadeServiceFeign#listProduct] connection: keep-alive
2022-07-10 16:36:09.595 DEBUG 18741 --- [nio-9090-exec-1] c.l.e.p.s.ProductFacadeServiceFeign      : [ProductFacadeServiceFeign#listProduct] content-type: application/json
2022-07-10 16:36:09.595 DEBUG 18741 --- [nio-9090-exec-1] c.l.e.p.s.ProductFacadeServiceFeign      : [ProductFacadeServiceFeign#listProduct] date: Sun, 10 Jul 2022 08:36:09 GMT
2022-07-10 16:36:09.595 DEBUG 18741 --- [nio-9090-exec-1] c.l.e.p.s.ProductFacadeServiceFeign      : [ProductFacadeServiceFeign#listProduct] keep-alive: timeout=60
2022-07-10 16:36:09.595 DEBUG 18741 --- [nio-9090-exec-1] c.l.e.p.s.ProductFacadeServiceFeign      : [ProductFacadeServiceFeign#listProduct] transfer-encoding: chunked
2022-07-10 16:36:09.595 DEBUG 18741 --- [nio-9090-exec-1] c.l.e.p.s.ProductFacadeServiceFeign      : [ProductFacadeServiceFeign#listProduct] 
2022-07-10 16:36:09.597 DEBUG 18741 --- [nio-9090-exec-1] c.l.e.p.s.ProductFacadeServiceFeign      : [ProductFacadeServiceFeign#listProduct] [{"id":1,"productName":"华为手机","productNum":300,"productPrice":4999.0},{"id":2,"productName":"小米手机","productNum":400,"productPrice":2999.0}]
2022-07-10 16:36:09.597 DEBUG 18741 --- [nio-9090-exec-1] c.l.e.p.s.ProductFacadeServiceFeign      : [ProductFacadeServiceFeign#listProduct] <--- END HTTP (155-byte body)

Feign工作流程

Feign针对@FeignClient的注解的接口,通过动态代理生成动态代理实现类。根据Spring Web Mvc注解构造Http请求。

Feign整合了Ribbon基于Ribbon进行负载均衡。

服务集群结合Eureka来,Ribbon从Eureka获取对应的服务的server list,交给Ribbon进行负载均衡。

Feign核心机制

@EnableFeignClients注解

@FeignClient注解

Feign通过@EnableFeignClients注解@Import(FeignClientsRegistrar.class)去扫描所有打了@FeignClient注解的接口。

扫描到@FeignClient注解标注的接口之后,会针对接口生成Feign动态代理,以及解析和处理Spring Web Mvc注解,比如@RequestMapping,@PathVarialbe,@RequestParam,基于Spring Web Mvc的注解,生成对应的http请求。

扫描@FeignClient注解的接口

Application启动类的@EnableFeignClients注解,会触发FeignClientsRegistrar(ImportBeanDefinitionRegistrar)#registerBeanDefinitions(),扫描@EnableFeignClients注解标注类所在包及子包下面的@FeignClient注解的接口。

@FeignClient:

@FeignClient注解标注一个接口,这个接口会被创建为一个REST client(发送restful请求的客户端),然后将这个REST client注入其他的组件(比如OrderService),如果启用Ribbon,会采用负载均衡的方式,来进行http请求的发送,可以用@RibbonClient标注一个配置类,在配置类里可以自定义Ribbon的ILoadBalancer。

@RibbonClient的名称必须与@FeignClient的名称一样的。

package center.leon.eurekaconsumerribbonfeignapi.product.config;

import com.netflix.loadbalancer.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.cloud.netflix.ribbon.RibbonClients;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author : Leon on XXM Mac
 * @since : create in 2022/7/11 9:51 AM
 */
@Configuration
public class ProductFacadeServiceRibbonConfig {

    @Bean
    public IRule iRule() {
        return new RandomRule();
    }
}
package center.leon.eurekaconsumerribbonfeignapi.product.server;

import center.leon.eurekaconsumerribbonfeignapi.product.config.ProductFacadeServiceFeignConfig;
import center.leon.eurekaconsumerribbonfeignapi.product.config.ProductFacadeServiceRibbonConfig;
import center.leon.eurekaproviderribbonfeignapi.product.service.ProductFacadeService;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.cloud.openfeign.FeignClient;

/**
 * @author : Leon on XXM Mac
 * @since : create in 2022/7/10 1:45 PM
 */
@RibbonClient(value = "eureka-provider-ribbon-feign-api-impl", configuration =
        ProductFacadeServiceRibbonConfig.class)
@FeignClient(value = "eureka-provider-ribbon-feign-api-impl", configuration =
        ProductFacadeServiceFeignConfig.class)
public interface ProductFacadeServiceFeign extends ProductFacadeService {
}

使用@FeignClient注解,让Feign对这个接口创建一个对应的动态代理,这个动态代理就是REST client,发送rest请求的客户端

@FeignClient:

value & name:要调用的那个服务名称;

url:指定要请求的地址(`http://localhost:8080`);

decode404:用404替代抛出FeignException异常;

configurtation:指定一个配置类,可以在里面自定义Encoder、Decoder、Contract。

@EnableFeignClients:

扫描标注@FeignClient注解的接口。

basePackages:指定要扫描的包路径。

@Import(FeignClientsRegistrar.class):

负责扫描@FeignClient注解,并将@FeignClient标注的接口注册到BeanDefinitionRegistry中。

FeignClientRegistrar & ImportBeanDefinitionRegistrar:

@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
      BeanDefinitionRegistry registry) {
   registerDefaultConfiguration(metadata, registry);
   registerFeignClients(metadata, registry);
}

注册默认的配置:这个是什么意思?一看就是解析相关的一些配置,注册到自己身体里去

注册FeignClients:扫描各包下面的@FeignClient注解,然后生成@FeignClient的动态代理。

Spring boot启动时,@Import机制调用FeignClientRegistrar.registerBeanDefinitions()方法,扫描@FeignClient注解标注的接口,生成对应的动态代理实例。