Spring Cloud(二)Feign源代码分析

版本

Feign的版本是10.11

一个例子

interface GitHub {
  @RequestLine("GET /repos/{owner}/{repo}/contributors")
  List<Contributor> contributors(@Param("owner") String owner, @Param("repo") String repo);
}

public static class Contributor {
  String login;
  int contributions;
}

public class MyApp {
  public static void main(String... args) {
    GitHub github = Feign.builder()
                         .decoder(new GsonDecoder())
                         .target(GitHub.class, "https://api.github.com");

    // Fetch and print a list of the contributors to this library.
    List<Contributor> contributors = github.contributors("OpenFeign", "feign");
    for (Contributor contributor : contributors) {
      System.out.println(contributor.login + " (" + contributor.contributions + ")");
    }
  }

简单地来说,Feign做了这么一件事:为接口GitHub生成动态的代码。生成的代码中的一个主要功能是结合接口Github的方法上的注解和实参来构建出一个Http请求,接着发送这个请求得到响应,最后将响应映射成我们代码指定的返回值类型。

Contract和MethodMetadata

Contract是一组约定,Feign为我们提供了一些注解比如RequestLine、Body、Param等等,Contract模块负责解析出这些注解并检查它们是否符合规范,然后它会目标接口(上面例子中的Github接口)的每一个方法创建一个MethodMetadata。

MethodMetadata如同字面上意思,就是每个方法的元数据。比如当方法被@RequestLine("GET /")注解,那么元数据中http方法为“GET”,url为“/”。Feign所发出的Http请求就是拼接这些元数据提供的数据和调用方提供的实参组合而成的。

MethodHandler

接口的(比如上面例子的Github)每个方法被调用后,最终都是通过一个MethodHandler将请求发出去,默认的MethodHandler实现是SynchronousMethodHandler。

FeignInvocationHandler

熟悉JDK动态代理的朋友一定都非常熟悉java.lang.reflect.InvocationHandler这个类,FeignInvocationHandler是这个类的子类。FeignInvocationHandler内部有一个非常重要的映射,名为dispatch,类型是Map<Method, MethodHandler>。接口的(比如上面例子的Github)每个方法被调用后,请求都会路由到FeignInvocationHandler类,并且利用动态代理传入的Method参数找到对应的MethodHandler(默认的MethodHandler实现是SynchronousMethodHandler)然后将请求发送出去。

ParseHandlersByName

这个类是根据给定接口(上面例子中的Github)生成一个映射,映射表的key是“类型+方法名+参数”,比如上例会有一个这样的key: "GitHub#contributors(String,String)";映射表的Value则是对应的MethodHandler的实例,默认情况下也就是SynchronousMethodHandler实例。

Map<Method, MethodHandler>

可能是出于性能的考虑,在ReflectiveFeign的newInstance方法中,会创建出类型为Map<Method, MethodHandler>的methodToHandler,这个methodToHandler就是前面讲到FeignInvocationHandler内部的dispatch。但是为什么在ParseHandlersByName中生成Map<String, MethodHandler>而不是直接生成Map<Method, MethodHandler>,我暂时也不知道。

RequestTemplate

这个类非常重要,因为请求Request就是从这个类的实例导出的。顾名思义,它是Request的模板,就像JSP模板一样,它包含了一些占位符,这些占位符存在于uri、headers、queries、body等等,RequestTemplate提供一个RequestTemplate resolve(Map<String, ?> variables)的方法来生成一个已经展开(resolved)的RequestTemplate。RequestTemplate如果想返回一个Request,那么它首先需要是resolved的。关于RequestTemplate更多的用法,可以参考Feign-Core的单元测试代码RequestTemplateTest。

RequestTemplate.Factory

RequestTemplate.Factory一共有3种:BuildTemplateByResolvingArgs、BuildFormEncodedTemplateFromArgs、BuildEncodedTemplateFromArgs,后两者继承于第一种。

  • BuildFormEncodedTemplateFromArgs主要是将MethodMetaData的formParams取出到一个Map<String, Object>中然后编码,接着再把结果放入template的body中。
  • BuildEncodedTemplateFromArgs将body(MethodMetaData的bodyIndex会指定哪个实参是body)编码,接着将结果放入template的body中。

那么这三个什么时候使用哪个?下面是代码:

        BuildTemplateByResolvingArgs buildTemplate;
        if (!md.formParams().isEmpty() && md.template().bodyTemplate() == null) {
          buildTemplate =
              new BuildFormEncodedTemplateFromArgs(md, encoder, queryMapEncoder, target);
        } else if (md.bodyIndex() != null) {
          buildTemplate = new BuildEncodedTemplateFromArgs(md, encoder, queryMapEncoder, target);
        } else {
          buildTemplate = new BuildTemplateByResolvingArgs(md, queryMapEncoder, target);
        }

这段判断语句比较复杂,我们用表格表示会更直观一点:

RequestTemplate.Factory md.formParams().isEmpty() md.template().bodyTemplate() md.bodyIndex()
BuildTemplateByResolvingArgs true not null null
BuildTemplateByResolvingArgs false not null null
BuildTemplateByResolvingArgs true null null
BuildEncodedTemplateFromArgs true not null not null
BuildEncodedTemplateFromArgs true null not null
BuildEncodedTemplateFromArgs false not null not null
BuildFormEncodedTemplateFromArgs false null not null
BuildFormEncodedTemplateFromArgs false null null

发送一次请求的过程中会创建多少个RequestTemplate?

两个,请看下面的调用链:
SynchronousMethodHandler::invoke -> BuildTemplateByResolvingArgs::create[创建第一个] -> BuildTemplateByResolvingArgs::resolve -> RequestTemplate::resolve[创建第二个]。

RequestInterceptor在RequestTemplate resolved之后还是之前调用?

之后。

什么条件一个变量才会成为FormParam?

interface TestInterface {
		@RequestLine("POST /")
		@Body("%7B\"customer_name\": \"{customer_name}\", \"user_name\": \"{user_name}\", \"password\": \"{password}\"%7D")
		void login(@Param("customer_name") String customer, @Param("user_name") String user,
				@Param("password") String password);
}

如上代码所示,customer_name、user_name、password都属于FormParam,因为它们虽然存在于Body中,但是却不存在于uri、header、queries中。我们修改下代码,增加一行如下:

interface TestInterface {
		@RequestLine("POST /")
		@Headers("X-FOO: {customer_name}")
		@Body("%7B\"customer_name\": \"{customer_name}\", \"user_name\": \"{user_name}\", \"password\": \"{password}\"%7D")
		void login(@Param("customer_name") String customer, @Param("user_name") String user,
				@Param("password") String password);
}

这样子,customer_name就不属于FormParam,因为在@Headers("X-FOO: {customer_name}")中有它。

FormParam所组成的body与注解@Body哪个优先级更高?

FormParam所组成的body优先级更高。

FormParam不能和实参中的隐式body共存

interface TestInterface {
	@RequestLine("POST /")
	void login2(@Param("customer_name") String customer, @Param("user_name") String user,
				@Param("password") String password, String bodyContent);

这段代码会在解析方法时报异常:“java.lang.IllegalStateException: Body parameters cannot be used with form parameters.”,上面代码中bodyContent和其他3个参数构建的body会产生冲突,所以才会报异常。

TBC...

posted @ 2020-11-26 21:18  ralgo  阅读(684)  评论(0)    收藏  举报