从源码上学习 SpringCloud - Feign

1、Feign 的工作原理

 

       Feign 是一种声明式、模板化的 Http 客户端,是一个伪 Java Http 客户端,Feign 不做任何的请求处理Feign 通过处理注解生成 Request 模板,从而简化了 Http API 的开发。开发人员可以使用注解的方式定制 Request API 模板。在发送 Http Request 请求之前,Feign 通过处理注解的方式替换掉 Request 模板中的参数,生成真正的 Request, 并交给 Java Http 客户端去处理。利用这种方式,开发者只需要关注 Feign 注解模板的开发,而不用关注 Http 请求本身,简化了 Http 请求的过程,使得 Http 请求变得简单和容易理解。

 

1.1、Feign 的源码实现过程

 
在这里插入图片描述

Feign 的源码实现过程如下 :

  • 1、首先通过 @EnableFeignClients 注解开启 FeignClient 的功能。只有这个注解存在,才会在程序启动时开启对@FeignClient注解的包扫描。
  • 2、根据 Feign 的规则实现接口,并在接口上面加上 @FeignClient 注解。
  • 3、程序启动后,会进行包扫描,扫描所有的 @FeignClient 的注解的类,并将这些信息注入Ioc容器中。
  • 4、当接口的方法被调用时,通过 JDK 的代理来生成具体的 RequestTemplate 模板对象。
  • 5、根据 RequestTemplate 再生成 Http 请求的 Request 对象。
  • 6、Request 对象交给 Client 去处理,其中 Client 的网络请求框架可以是 HttpURLConnection、HttpClient 和 OkHttp.
  • 7、最后 Client 被封装到 LoadBalanceClient 类,这个类结合类 Ribbon 做到负载均衡。

 

1.2、源码角度分析 Feign 工作原理

 

  • Feign 通过包扫描注入 FeignClient 的 Bean。在程序启动时,会检查是否有 @EnableFeignClients 注解,如果有该注解就开启包扫描,扫描被 @FeignClient 注解的接口。源码在 FeignClientRegistrar 类中。

 

2、源码看 FeignClient 注解

 

       @FeignClient 注解用于创建声明式 API 接口,该接口是RESTful 风格的. Feign 被设计成插拔式的,可以注入其他组件和 Feign 一起使用。最典型的是如果 Ribbon 可用, Feign 会和 Ribbon 结合进行负载均衡。

 

  • 以下是 FeignClient 注解类源码,在该源码上添加了详细注解便于理解。
package org.springframework.cloud.netflix.feign;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;

// @Target({ElementType.TYPE}),表示 FeignClient 注解的作用目标在接口上。
@Target({ElementType.TYPE})

// @Retention(RetentionPolicy.RUNTIME) 表示该注解会在Class字节码文件中存在,在运行时可以通过反射获取到。
@Retention(RetentionPolicy.RUNTIME)

// @Documented 表示该注解将被包含在 Javadoc 中
@Documented
public @interface FeignClient {

    /**
     * value 和 name 一样,是被调用的服务的 ServiceId
     */
    @AliasFor("name")
    String value() default "";

    /** @deprecated */
    @Deprecated
    String serviceId() default "";


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

    /**
     *
     */
    String qualifier() default "";

    /**
     * url : url一般用于调试,可以手动指定 @FeignClient 调用的地址。
     */
    String url() default "";

    /**
     * decode404 : 当发生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 的统一前缀
     */
    String path() default "";

    boolean primary() default true;
}

 

  • FeignClient 注解被@Target(ElementType.TYPE)修饰,表示 FeignClient 注解的作用目标在接口上。
     
  • @Retention(RetentionPolicy.RUNTIME)注解表明该注解会在 Class 字节码文件中存在,在运行时可以通过反射获取到。
     
  • @Documented 表示该注解将被包含在 Javadoc 中。
     
  • value() 和 name() 一样,是被调用的服务的 ServiceId.
     
  • url() 直接填写硬编码的 Url 地址
     
  • configuration() 指明 FeignClient 配置类,默认的配置类为 FeignClientsConfiguration 。在缺省的情况下,这个类注入了默认的 Decoder、Encoder、Contract 等配置的 Bean.
     
  • fallback() 为配置熔断器的处理类。

 

3、FeignClient 的配置

 

3.1 默认配置 FeignClientsConfiguration

 

  • 关于Feign Client 默认的配置类:FeignClientsConfiguration

       Feign Client 默认的配置类为 FeignClientsConfiguration ,这个类注入了很多 Feign 相关的配置 Bean ,包括 FeignRetryer、FeignLoggerFactory 和 FormattingConversionService 等。另外 Decoder、Encoder 和 Contract 这3个类在没有 Bean 被注入的情况下,会自动注入默认配置的 Bean,即 ResponseEntityDecoder、SpringEncoder 和 SpringMvcContract.
 
@ConditionalOnMissingBean 注解表示如果没有注入该类的 Bean 会默认注入一个 Bean.

  • 相关源码已添加注释,以便于理解,详细如下
package org.springframework.cloud.netflix.feign;

import com.netflix.hystrix.HystrixCommand;
import feign.Contract;
import feign.Feign;
import feign.Logger;
import feign.Retryer;
import feign.Feign.Builder;
import feign.codec.Decoder;
import feign.codec.Encoder;
import feign.hystrix.HystrixFeign;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.web.HttpMessageConverters;
import org.springframework.cloud.netflix.feign.support.ResponseEntityDecoder;
import org.springframework.cloud.netflix.feign.support.SpringDecoder;
import org.springframework.cloud.netflix.feign.support.SpringEncoder;
import org.springframework.cloud.netflix.feign.support.SpringMvcContract;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import org.springframework.core.convert.ConversionService;
import org.springframework.format.support.DefaultFormattingConversionService;
import org.springframework.format.support.FormattingConversionService;

@Configuration
public class FeignClientsConfiguration {
    @Autowired
    private ObjectFactory<HttpMessageConverters> messageConverters;
    @Autowired(
        required = false
    )
    private List<AnnotatedParameterProcessor> parameterProcessors = new ArrayList();
    @Autowired(
        required = false
    )
    private List<FeignFormatterRegistrar> feignFormatterRegistrars = new ArrayList();
    @Autowired(
        required = false
    )
    private Logger logger;

    public FeignClientsConfiguration() {
    }

    /**
     * Decoder feignDecoder 没有注入该类的 Bean 会默认注入一个 ResponseEntityDecoder.
     */
    @Bean
    @ConditionalOnMissingBean
    public Decoder feignDecoder() {
        return new ResponseEntityDecoder(new SpringDecoder(this.messageConverters));
    }

    /**
     * Encoder feignEncoder 没有注入该类的 Bean 会默认注入一个 SpringEncoder.
     */
    @Bean
    @ConditionalOnMissingBean
    public Encoder feignEncoder() {
        return new SpringEncoder(this.messageConverters);
    }

    /**
     * Contract feignContract 没有注入该类的 Bean 会默认注入一个 SpringMvcContract.
     */
    @Bean
    @ConditionalOnMissingBean
    public Contract feignContract(ConversionService feignConversionService) {
        return new SpringMvcContract(this.parameterProcessors, feignConversionService);
    }

    @Bean
    public FormattingConversionService feignConversionService() {
        FormattingConversionService conversionService = new DefaultFormattingConversionService();
        Iterator var2 = this.feignFormatterRegistrars.iterator();

        while(var2.hasNext()) {
            FeignFormatterRegistrar feignFormatterRegistrar = (FeignFormatterRegistrar)var2.next();
            feignFormatterRegistrar.registerFormatters(conversionService);
        }

        return conversionService;
    }

    @Bean
    @ConditionalOnMissingBean
    public Retryer feignRetryer() {
        return Retryer.NEVER_RETRY;
    }

    @Bean
    @Scope("prototype")
    @ConditionalOnMissingBean
    public Builder feignBuilder(Retryer retryer) {
        return Feign.builder().retryer(retryer);
    }

    /**
     * FeignLoggerFactory feignLoggerFactory 没有注入该类的 Bean 会默认注入一个 DefaultFeignLoggerFactory.
     */
    @Bean
    @ConditionalOnMissingBean({FeignLoggerFactory.class})
    public FeignLoggerFactory feignLoggerFactory() {
        return new DefaultFeignLoggerFactory(this.logger);
    }

    @Configuration
    @ConditionalOnClass({HystrixCommand.class, HystrixFeign.class})
    protected static class HystrixFeignConfiguration {
        protected HystrixFeignConfiguration() {
        }

        @Bean
        @Scope("prototype")
        @ConditionalOnMissingBean
        @ConditionalOnProperty(
            name = {"feign.hystrix.enabled"},
            matchIfMissing = false
        )
        public Builder feignHystrixBuilder() {
            return HystrixFeign.builder();
        }
    }
}

 

3.2 FeignClient 的自定义配置

 

// todo……

 

4、Feign 中使用 HttpClient 和 OkHttp

 

       在 Feign中,Client 是一个非常重要的组件,Feign 最终发送 Request 请求以及接收 Response 响应都是由 Client 组件 完成的。Client 在 Feign 源码中是一个接口,在默认情况下 Client 的实现类都是Client.Default。
 
       Client.Default 是由 JDK 原生的 HttpURLConnection 来发送 HTTP 请求,没有连接池,但是对每个地址会保持一个长连接,即利用 Http 的 persistence connection.
 
       另外,Client 还支持 HttpClientOkHttp 来进行网络请求。

  • 可以通过 Apache 的 HttpClient 替换 Feign 原始的 HTTP Client,通过设置连接池、超时时间等对服务之间的调优调用。
  • 也可以使用 okHttp 替换 feign 默认的 Client。okHttp 支持 SPDY,可以合并多个到同一个主机的请求。如果 SPDY可以的话,还可以通过okHttp使用连接池技术减少请求的延迟。当然 okHttp 还可以使用 GZIP 压缩减少传输的数据量,以及缓存响应避免重复的网络请求。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值