讲一下我理解的feign

357 阅读6分钟

文章概览

  1. clone feign的x.x.x版本分支进行一个DEMO尝试
  2. 简单分析feign的构造原理

feign入门

  1. feign是一个声明式的http客户端工具,帮助开发者更好,更优雅地去处理http的请求和响应
  2. 版本说明
    • spring cloud starter openfeign [2.1.3.RELEASE]版本依赖于feign的10.2.3版本,所以接下来说有的内容都是基于10.2.3版本的feign

feign的项目构成

  • 目录机构
-- benchmark //性能测试用例
-- core //feign的核心包
-- example-xx * 2 //示例包
// 下面均为对一些第三方模块的集成
-- gson
-- httpclient
-- hystrix
-- ....

feign -hello world

  • 利用feign完成一次http请求(以下代码来自 feign-core/src/main/java/test/BaseApiTest.java)
public class BaseApiTest {
  @Rule
  public final MockWebServer server = new MockWebServer();
  interface BaseApi<K, M> {
    @RequestLine("GET /api/{key}")
    Entity<K, M> get(@Param("key") K key);
  }

  static class Entity<K, M> {
    K key;
    M model;
  }

  interface MyApi extends BaseApi<String, Long> {
  }

  @Test
  public void resolvesParameterizedResult() throws InterruptedException {
    // 往mock队列里丢进一个mock响应
    server.enqueue(new MockResponse().setBody("foo"));
    // 构造一个请求路径
    String baseUrl = server.url("/default").toString();
    // 构造一个客户端并且进行访问
    Feign.builder()
        .decoder(new Decoder() {
          @Override
          public Object decode(Response response, Type type) {
            assertThat(type)
                .isEqualTo(new TypeToken<Entity<String, Long>>() {}.getType());
            return null;
          }
        })
        .target(MyApi.class, baseUrl).get("foo");
    // 断言访问路径是否吻合
    assertThat(server.takeRequest()).hasPath("/default/api/foo");
  }
}
  • 如何快速入门feign的使用
    • 通过feign-core下的test用例,可以完成feign大多数功能特性的使用

feign的构造原理

先说总结

  • feign的客户端实例是动态代理构建的

构建一个最简单的feign客户端

public class MyTest {
    interface Api {
        @RequestLine("GET /")
        String get();
    }
    @Test
    public void test() {
        Api target = Feign.builder().target(Api.class, "https://www.baidu.com");
        String resp = target.get();
        System.out.println(resp);
    }
}

Feign.builder()

  • builder()方法实际上是返回了一个feign.Feign.Builder
  • Builder有什么东西?以上述demo为例子,我只传入target属性,那么其他应该为默认
// 请求拦截器列表
private final List<RequestInterceptor> requestInterceptors =
    new ArrayList<RequestInterceptor>();
// 默认日志级别是NONE,可选列表有NONE,BASIC,HEADERS,FULL,选择FULL会把请求报文和响应报文均输出
private Logger.Level logLevel = Logger.Level.NONE;
// contract是合约,约定的意思,其实就是定义feign的客户端接口在编写时候的一些注解规范
// 可以通过ContractWithRuntimeInjectionTest/DefaultContractTest进行深入学习
private Contract contract = new Contract.Default();
// 可以理解为真正执行http请求的实现,默认实现是基于JDK内置的java.net,javax.net
// 在spring cloud的体系中,在feign引入负载均衡,是从这个client去修改的
private Client client = new Client.Default(null, null);
// 重试策略 void continueOrPropagate(RetryableException e);/Retryer clone();
// 如果需要实现自定义重试,那么需要去实现这两个接口
// feign提供了两种重试策略,feign.Retryer#NEVER_RETRY,feign.Retryer.Default
// feign默认的重试策略是计数失败和休眠重试
private Retryer retryer = new Retryer.Default();
// 日志实现,这个没啥好说的
private Logger logger = new NoOpLogger();
// 编码器,主要作用是对接口参数进行序列化
private Encoder encoder = new Encoder.Default();
private Decoder decoder = new Decoder.Default();
// 这个是做参数的key value映射
private QueryMapEncoder queryMapEncoder = new QueryMapEncoder.Default();
// 主要是对http状态码>=400的情况下做异常转换抛出处理
private ErrorDecoder errorDecoder = new ErrorDecoder.Default();
// options用于定义一些超时时间,比如连接超时,等待响应超时
private Options options = new Options();
// 这个主要是动态代理的执行器
// 由于feign是针对接口进行编程的,我们并没有提供实现,默认实现是feign提供的,走动态代理
// 那一套之后就需要一些InvocationHandler的实现
// 后续一些断路器的接入本质上是通过实现这个东西来实现的
private InvocationHandlerFactory invocationHandlerFactory =
    new InvocationHandlerFactory.Default();
private boolean decode404;
private boolean closeAfterDecode = true;
private ExceptionPropagationPolicy propagationPolicy = NONE;
  • T target(Class apiType, String url)
    • 调用这个方法后,就返回一个代理类了,是一个真正可执行的http客户端类
    • 简述 feign.Target
public interface Target<T> {
  // 返回接口类型,也就是我们的声明式interface
  Class<T> type();
  // 返回最终代理类的命名,默认跟url一致
  String name();
  // 返回请求域名地址
  String url();
  // 请求模板类,返回最终的请求类,需要经过拦截器列表的处理
  public Request apply(RequestTemplate input)
 }
  • feign提供了2个默认实现
    • feign.Target.HardCodedTarget
      • apply方法会对最终url进行协议修正(feign.Target.HardCodedTarget#apply)
    • feign.Target.EmptyTarget
  • build().newInstance(target); // 这是target的实现,这里要分两步来看
    • feign.Feign.Builder#build
// 构建一个工厂类,用来创建方法处理器
// 什么是方法处理器,其实就针对 我们声明的接口 里的方法进行一个路由
SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
    new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,
        logLevel, decode404, closeAfterDecode, propagationPolicy);
// 解析处理器
ParseHandlersByName handlersByName =
    new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder,
        errorDecoder, synchronousMethodHandlerFactory);
// 返回一个Feign的子类,具体是实现了newInstance的方法
return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
  • feign.ReflectiveFeign.ParseHandlersByName#apply
// 对接口进行解析
public Map<String, MethodHandler> apply(Target key) {
  // 这一段代码就是对方法进行一些解析返回方法元数据
  // 比如返回类型,参数类型,方法注解,参数注解等
  // 所以复写constract就可以适配spring mvc的注解
  List<MethodMetadata> metadata = contract.parseAndValidatateMetadata(key.type());
  Map<String, MethodHandler> result = new LinkedHashMap<String, MethodHandler>();
  for (MethodMetadata md : metadata) {
    // 省略N行代码
    // 构建每个接口方法对应的方法处理器feign.SynchronousMethodHandler
    result.put(md.configKey(),
        factory.create(key, md, buildTemplate, options, decoder, errorDecoder));
  }
  return result;
}
  • feign.Feign#newInstance(这个方法可以说是了解feign运行机制的最重要的方法了)
// target就是我们对接口进行包装的一个类,现在是对这个target实现类进行解析
// 主要是解析接口里的方法,方法上的注解,构造成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 是JDK动态代理需要的一个参数类,主要是做方法回调的
// feign.InvocationHandlerFactory.Default 这是feign默认提供的一个执行处理器工厂
// 最后返回的实例是feign.ReflectiveFeign.FeignInvocationHandler
InvocationHandler handler = factory.create(target, methodToHandler);
T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
    new Class<?>[] {target.type()}, handler);
// 默认方法去绑定代理
for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
  defaultMethodHandler.bindTo(proxy);
}
// 返回代理
return proxy;
  • feign.ReflectiveFeign.FeignInvocationHandler#invoke
static class FeignInvocationHandler implements InvocationHandler {

  private final Target target;
  private final Map<Method, MethodHandler> dispatch;

  FeignInvocationHandler(Target target, Map<Method, MethodHandler> dispatch) {
    this.target = checkNotNull(target, "target");
    this.dispatch = checkNotNull(dispatch, "dispatch for %s", target);
  }
  // 对接口的每一次方法调用都会先进入invoke
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    if ("equals".equals(method.getName())) {
      try {
        Object otherHandler =
            args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
        return equals(otherHandler);
      } catch (IllegalArgumentException e) {
        return false;
      }
    } else if ("hashCode".equals(method.getName())) {
      return hashCode();
    } else if ("toString".equals(method.getName())) {
      return toString();
    }
    // 这个地方就是真正执行的逻辑,这是上一步newInstance里绑定的  方法-方法处理器 集合
    // 根据方法获取到处理器,然后就可以执行真正的逻辑
    return dispatch.get(method).invoke(args);
  }
  • 讲到这里,整个脉络就清晰了

执行方法真正逻辑

  • 回顾上面的DEMO代码
// 这个过程我们在上面进行了分析
Api target = Feign.builder().target(Api.class, "https://www.baidu.com");
// 这里就是真正逻辑的执行,那么get方法就会进入到FeignInvocationHandler 的invoke
// invoke会从dispatch选取到合适的methodHandler进行invoke
String resp = target.get();
  • feign.SynchronousMethodHandler#invoke
@Override
public Object invoke(Object[] argv) throws Throwable {
  // 这个请求模板是上面构建好的
  RequestTemplate template = buildTemplateFromArgs.create(argv);
  // 重试器
  Retryer retryer = this.retryer.clone();
  // 只有while true才能进行重试
  while (true) {
    try {
      // 执行编码 -》 请求发出 -》 响应解码
      return executeAndDecode(template);
    } catch (RetryableException e) {
      try {
        // 默认的重试器是会计数和休眠,上面已经说了
        retryer.continueOrPropagate(e);
      } catch (RetryableException th) {
        Throwable cause = th.getCause();
        if (propagationPolicy == UNWRAP && cause != null) {
          throw cause;
        } else {
          throw th;
        }
      }
      if (logLevel != Logger.Level.NONE) {
        logger.logRetry(metadata.configKey(), logLevel);
      }
      continue;
    }
  }
}