前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Feign其实也不是很难

Feign其实也不是很难

作者头像
用户3467126
发布2021-08-05 10:40:24
5360
发布2021-08-05 10:40:24
举报
文章被收录于专栏:爱编码爱编码

Feign是什么

Feign是一个http请求调用的轻量级框架,可以以Java接口注解的方式调用Http请求。Spring Cloud引入 Feign并且集成了Ribbon实现客户端负载均衡调用。

封装了Http调用流程,更适合面向接口化的变成习惯

Feign基本流程

核心就是通过一系列的封装和处理,将以JAVA注解的方式定义的远程调用API接口,最终转换成HTTP的请求形式,然后将HTTP的请求的响应结果,解码成JAVA Bean,放回给调用者。

Feign源码实现

1. 基于面向接口的动态代理方式生成实现类

@EnableFeignClients开启扫描所有@FeignClient注解的接口,将所有的访问路径以及接口信息封装成一个FeignClientFactoryBean注册到Spring容器中。

在使用feign 时,会定义对应的接口类,在接口类上使用Http相关的注解,标识HTTP请求参数信息

在Feign 底层,通过基于面向接口的动态代理方式生成实现类,将请求调用委托到动态代理实现类,基本原理如下所示:

代码语言:javascript
复制
 public class ReflectiveFeign extends Feign{
  ///省略部分代码
  @Override
  public <T> T newInstance(Target<T> target) {
    //根据接口类和Contract协议解析方式,解析接口类上的方法和注解,转换成内部的MethodHandler处理方式
    Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
    Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
    List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();

    for (Method method : target.type().getMethods()) {
      if (method.getDeclaringClass() == Object.class) {
        continue;
      } else if(Util.isDefault(method)) {
        DefaultMethodHandler handler = new DefaultMethodHandler(method);
        defaultMethodHandlers.add(handler);
        methodToHandler.put(method, handler);
      } else {
        methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
      }
    }
    InvocationHandler handler = factory.create(target, methodToHandler);
    // 基于Proxy.newProxyInstance 为接口类创建动态实现,将所有的请求转换给InvocationHandler 处理。
    T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(), new Class<?>[]{target.type()}, handler);

    for(DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
      defaultMethodHandler.bindTo(proxy);
    }
    return proxy;
  }
  //省略部分代码

为了创建Feign的远程接口的代理实现类,Feign提供了自己的一个默认的调用处理器,叫做 FeignInvocationHandler 类,该类处于 feign-core 核心jar包中。当然,调用处理器可以进行替换,如果Feign与Hystrix结合使用,则会替换成 HystrixInvocationHandler 调用处理器类,类处于 feign-hystrix 的jar包中

2. 根据Contract协议规则,解析接口类的注解信息,解析成内部表现:

代码语言:javascript
复制
/**
 * Defines what annotations and values are valid on interfaces.
 */
public interface Contract {

  /**
   * Called to parse the methods in the class that are linked to HTTP requests.
   * 传入接口定义,解析成相应的方法内部元数据表示
   * @param targetType {@link feign.Target#type() type} of the Feign interface.
   */
  // TODO: break this and correct spelling at some point
  List<MethodMetadata> parseAndValidatateMetadata(Class<?> targetType);
}

3.基于 RequestBean,动态生成Request

根据传入的Bean对象和注解信息,从中提取出相应的值,来构造Http Request 对象

4.使用Encoder 将Bean转换成 Http报文正文(消息解析和转码逻辑)

Feign 最终会将请求转换成Http 消息发送出去,传入的请求对象最终会解析成消息体,

5.拦截器负责对请求和返回进行装饰处理

在请求转换的过程中,Feign 抽象出来了拦截器接口,用于用户自定义对请求的操作,比如,如果希望Http消息传递过程中被压缩,可以定义一个请求拦截器。

代码语言:javascript
复制
public interface RequestInterceptor {

  /**
   * 可以在构造RequestTemplate 请求时,增加或者修改Header, Method, Body 等信息
   * Called for every request. Add data using methods on the supplied {@link RequestTemplate}.
   */
  void apply(RequestTemplate template);
}

比如,如果希望Http消息传递过程中被压缩,可以定义一个请求拦截器:

代码语言:javascript
复制

public class FeignAcceptGzipEncodingInterceptor extends BaseRequestInterceptor {

 /**
  * Creates new instance of {@link FeignAcceptGzipEncodingInterceptor}.
  *
  * @param properties the encoding properties
  */
 protected FeignAcceptGzipEncodingInterceptor(FeignClientEncodingProperties properties) {
  super(properties);
 }

 /**
  * {@inheritDoc}
  */
 @Override
 public void apply(RequestTemplate template) {
  //  在Header 头部添加相应的数据信息
  addHeader(template, HttpEncoding.ACCEPT_ENCODING_HEADER, HttpEncoding.GZIP_ENCODING,
    HttpEncoding.DEFLATE_ENCODING);
 }
}

6.日志记录

在发送和接收请求的时候,Feign定义了统一的日志门面来输出日志信息 , 并且将日志的输出定义了四个等级:NONE、BASIC、HEADERS、FULL

具体实现位于:feign.Logger

7.基于重试器发送HTTP请求

Feign 内置了一个重试器,当HTTP请求出现IO异常时,Feign会有一个最大尝试次数发送请求

代码语言:javascript
复制
final class SynchronousMethodHandler implements MethodHandler {

  // 省略部分代码

  @Override
  public Object invoke(Object[] argv) throws Throwable {
   //根据输入参数,构造Http 请求。
    RequestTemplate template = buildTemplateFromArgs.create(argv);
    // 克隆出一份重试器
    Retryer retryer = this.retryer.clone();
    // 尝试最大次数,如果中间有结果,直接返回
    while (true) {
      try {
        return executeAndDecode(template);
      } catch (RetryableException e) {
        retryer.continueOrPropagate(e);
        if (logLevel != Logger.Level.NONE) {
          logger.logRetry(metadata.configKey(), logLevel);
        }
        continue;
      }
    }
  }

8.发送Http请求

Feign 真正发送HTTP请求是委托给 feign.Client 来做的。

代码语言:javascript
复制
public interface Client {

  /**
   * Executes a request against its {@link Request#url() url} and returns a response.
   *  执行Http请求,并返回Response
   * @param request safe to replay.
   * @param options options to apply to this request.
   * @return connected response, {@link Response.Body} is absent or unread.
   * @throws IOException on a network error connecting to {@link Request#url()}.
   */
  Response execute(Request request, Options options) throws IOException;
  }

Feign 默认底层通过JDK 的 java.net.HttpURLConnection 实现了feign.Client接口类,在每次发送请求的时候,都会创建新的HttpURLConnection 链接,这也就是为什么默认情况下Feign的性能很差的原因。可以通过拓展该接口,使用Apache HttpClient 或者OkHttp3等基于连接池的高性能Http客户端。

而这个client会委托给org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient进行负载均衡地调用,采用了观察者模式。

代码语言:javascript
复制

public class LoadBalancerFeignClient implements Client {
//省略.....
@Override
 public Response execute(Request request, Request.Options options) throws IOException {
  try {
   URI asUri = URI.create(request.url());
   String clientName = asUri.getHost();
   URI uriWithoutHost = cleanUrl(request.url(), clientName);
   FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(
     this.delegate, request, uriWithoutHost);

   IClientConfig requestConfig = getClientConfig(options, clientName);
   return lbClient(clientName)
     .executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse();
  }
  catch (ClientException e) {
   IOException io = findIOException(e);
   if (io != null) {
    throw io;
   }
   throw new RuntimeException(e);
  }
 }
//省略.....
}

参考文章

https://www.cnblogs.com/crazymakercircle/p/11965726.html https://www.jianshu.com/p/e0218c142d03 https://www.bilibili.com/video/BV1Ap4y167U5

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-07-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 爱编码 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Feign基本流程
  • Feign源码实现
    • 1. 基于面向接口的动态代理方式生成实现类
      • 2. 根据Contract协议规则,解析接口类的注解信息,解析成内部表现:
        • 3.基于 RequestBean,动态生成Request
          • 4.使用Encoder 将Bean转换成 Http报文正文(消息解析和转码逻辑)
            • 5.拦截器负责对请求和返回进行装饰处理
              • 6.日志记录
                • 7.基于重试器发送HTTP请求
                  • 8.发送Http请求
                  • 参考文章
                  相关产品与服务
                  负载均衡
                  负载均衡(Cloud Load Balancer,CLB)提供安全快捷的流量分发服务,访问流量经由 CLB 可以自动分配到云中的多台后端服务器上,扩展系统的服务能力并消除单点故障。负载均衡支持亿级连接和千万级并发,可轻松应对大流量访问,满足业务需求。
                  领券
                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档