不是吧不是吧?还知道Open Feign原理?

263 阅读29分钟

什么是OpenFeign

OpenFeign目前是Spring Cloud 二级子项目。平时说的Feign指的是Netflix下的Feign,现在我们使用的是OpenFeign,是Spring提供的。
OpenFeign是一种声明式、模板化的HTTP客户端(仅在Application Client中使用)(称OpenFeign作用:声明式服务调用)。声明式调用是指,就像调用本地方法一样调用远程方法,无需感知操作远程http请求。使用OpenFeign后可以不使用RestTemplate进行调用。
Spring Cloud的声明式调用, 可以做到使用 HTTP请求远程服务时能就像调用本地方法一样的体验,开发者完全感知不到这是远程方法,更感知不到这是个HTTP请求。
Feign的应用,让Spring Cloud微服务调用像Dubbo一样,Application Client直接通过接口方法调用Application Service,而不需要通过常规的RestTemplate构造请求再解析返回数据。
它解决了让开发者调用远程接口就跟调用本地方法一样,无需关注与远程的交互细节,更无需关注分布式环境开发。
使用OpenFeign时就好像在写控制器方法,OpenFeign都是写在接口中,在声明的方法上添加SpringMVC注解或声明的参数上添加SpringMVC注解就可以完成调用远程的控制器方法。
Feign集成了Ribbon
利用Ribbon维护了Payment的服务列表信息,并且通过轮询实现了客户端的负载均衡。
而与Ribbon不同的是,通过Feign只需要定义服务绑定接口且以声明式的方法,优雅而简单的实现了服务调用。

Netflix Feign还是Open Feign?

首先需要明确:他俩是属于同一个东西,Feign属于Netflix开源的组件。Netflix Feign仅仅只是改名成为了Open Feign而已,然后Open Feign项目在其基础上继续发展至今。

OpenFeign项目

Fegin的核心注解@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) @Retention(RetentionPolicy.RUNTIME) @Documented 
public @interface FeignClient { 
  @AliasFor("name") String value() default ""; 
  @Deprecated String serviceId() default "";
  @AliasFor("value") 
  String name() default ""; 
  String qualifier() default ""; 
  String url() default ""; 
  boolean decode404() default false; 
  Class<?>[] configuration() default {}; 
  Class<?> fallback() default void.class; 
  Class<?> fallbackFactory() default void.class; 
  String path() default ""; 
  boolean primary() default true; 
}

name: 指定Feign Client的名称,如果项目使用了 Ribbon,name属性会作为微服务的名称,用于服务发现。

serviceId: 用serviceId做服务发现已经被废弃,所以不推荐使用该配置。

value: 指定Feign Client的serviceId,如果项目使用了 Ribbon,将使用serviceId用于服务发现,但上面可以看到serviceId做服务发现已经被废弃,所以也不推荐使用该配置。

qualifier: 为Feign Client 新增注解@Qualifier

url: 请求地址的绝对URL,或者解析的主机名

decode404: 调用该feign client发生了常见的404错误时,是否调用decoder进行解码异常信息返回,否则抛出FeignException。

fallback: 定义容错的处理类,当调用远程接口失败或超时时,会调用对应接口的容错逻辑,fallback 指定的类必须实现@FeignClient标记的接口。实现的法方法即对应接口的容错处理逻辑。

fallbackFactory: 工厂类,用于生成fallback 类示例,通过这个属性我们可以实现每个接口通用的容错逻辑,减少重复的代码。

path: 定义当前FeignClient的所有方法映射加统一前缀。

primary: 是否将此Feign代理标记为一个Primary Bean,默认为ture

1.1 创建Service端 添加依赖

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.5.1</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
    <java.version>1.8</java.version>
    <spring-cloud.version>2020.0.3</spring-cloud.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>${spring-cloud.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

1.2编写配置文件

server.port=8080
spring.application.name=openfeign-service

1.3增加控制器

2 新建Application Client项目 添加依赖

// 与service端区别增加 spring-cloud-starter-openfeign 包
项目启动增加 @EnableFeignCleints 注解
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.5.1</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
    <java.version>1.8</java.version>
    <spring-cloud.version>2020.0.3</spring-cloud.version>
</properties>
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter</artifactId>
    </dependency>
    <!-- 增加 openfeign包 -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>${spring-cloud.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

2.1新建OpenFeign接口

// 可以根据name 为必填 主要为客户端指定名称
// url 调用service地址
// 配置了qualifier优先用qualifier 主要为了防止重复的bean
@FeignClient(name = "demo", url = "localhost:8080/demo")
public interface DemoFeign {
    @RequestMapping("demo1")
    String demo1();

    @RequestMapping("demo2")
    Demo demo2();
}

使用OpenFeign访问带有参数的控制器

如果Feign接口中方法参数没有写注解,表示把该参数值设置到请求体中,在Application Service方参数必须添加@RequestBody接收。
但是由于请求体数据特性,Feign接口方法最多只能出现一个不带有注解的参数。
否则出现违法状态异常。
但是允许Feign接口方法参数列表中,一个参数不带有注解,其他都带有注解,表示不带有注解的参数设置到请求体中,其他参数为普通表单参数。

2传递请求体数据

2.1.1 application service方

请求体中数据可以是一个实体类,也可以是集合,更可以是简单数据类型。

@RequestMapping("demo3")
public String demo3(@RequestBody Map<String,Object> map) {
    return map.toString();
}

IDEA .http调用 与POSTMAN一样
POST http://localhost:8080/demo/demo3
Accept: */*
Cache-Control: no-cache
Content-Type: application/json

{
  "orderId": "100",
  "orderName": "test1",
  "orderMoney": "111"
}

2.1.2 application client 方

// Feign接口中方法参数都没有@RequestParam注解或者@RequestBody 注解
// 既可以含请求体数据,又包含普通表单数据

单个方法参数可以不需要增加注解
@RequestMapping("demo3")
String demo3(Map<String,Object> map);

2.2既包含请求体数据,又包含普通表单数据

2.2.1application service方
@RequestMapping("demo4")
public String demo4(@RequestBody Map<String, Object> map, @RequestParam String a) {
    System.out.println(map.toString() + ", " + a);
    return map.toString() + ", " + a;
}

2.2.2 application client 方 多入参

多个参数默认没有增加注解的为body体、其他参数必须增加对应的注解 
// 若果参数大于一个需要其他参数为普通表单参数。
Unexpected exception during bean creation; nested exception is java.lang.IllegalStateException: Method has too many Body
// body体 与表单提交方式
String demo4(Map<String, Object> map, @RequestParam String a);

3 Restful方式

SpringMVC支持Restful请求方式,所以在Feign接口中可以按照restful传递参数
3.1.1 application service方
@RequestMapping("/demo5/{name}/{age}")
public String demo5(@PathVariable String name, @PathVariable int age) {
    return name + "," + age;
}
3.1.2 application client 方
@RequestMapping("/demo5/{name}/{age}")
String demo5(@PathVariable String name, @PathVariable int age);

优化

OpenFeign通讯优化 GZIP

gzip介绍:gzip是一种数据格式,采用用deflate算法压缩数据;gzip是一种流行的数据压缩算法,应用十分广泛,尤其是在Linux平台。
gzip能力:当Gzip压缩到一个纯文本数据时,效果是非常明显的,大约可以减少70%以上的数据大小。
gzip作用:网络数据经过压缩后实际上降低了网络传输的字节数,最明显的好处就是可以加快网页加载的速度。
网页加载速度加快的好处不言而喻,除了节省流量,改善用户的浏览体验外,另一个潜在的好处是Gzip与搜索引擎的抓取工具有着更好的关系。例如 Google就可以通过直接读取gzip文件来比普通手工抓取更快地检索网页。

HTTP协议中关于压缩传输的规定(原理)

第一:客户端向服务器请求头中带有:Accept-Encoding:gzip, deflate 字段,向服务器表示,客户端支持的压缩格式(gzip或者deflate),如果不发送该消息头,服务器是不会压缩的。
第二:服务端在收到请求之后,如果发现请求头中含有Accept-Encoding字段,并且支持该类型的压缩,就对响应报文压缩之后返回给客户端,并且携带Content-Encoding:gzip消息头,表示响应报文是根据该格式压缩过的。
第三:客户端接收到响应之后,先判断是否有Content-Encoding消息头,如果有,按该格式解压报文。否则按正常报文处理。

image.png image.png

在OpenFeign技术中应用GZIP压缩

在整体流程中,如果使用GZIP压缩来传输数据,涉及到两次请求-应答。而这两次请求-应答的连接点是Application Client,那么我们需要在Application Client中配置开启GZIP压缩,来实现压缩数据传输。

只配置OpenFeign请求-应答的GZIP压缩

在交互数据量级不够的时候,看不到压缩内容。
这里只开启Feign请求-应答过程中的GZIP,也就是浏览器-Application Client之间的请求应答不开启GZIP压缩。
在全局配置文件中,使用下述配置来实现OpenFeign请求-应答的GZIP压缩
# feign gzip

# 开启请求GZIP
feign.compression.request.enabled=true
# 开启响应GZIP
feign.compression.response.enabled=true
# 设置支持GZIP压缩的MIME类型,即请求/响应类型。
feign.compression.request.mime-types=text/html,application/xml,application/json
# 配置启动压缩数据的最小阀值,单位字节。默认为2048
feign.compression.request.min-request-size=512

连接池

http 的背景原理
a. 两台服务器建立 http 连接的过程是很复杂的一个过程,涉及到多个数据包的交换,并且也很耗时间。
b. Http 连接需要的 3 次握手 4 次分手开销很大,这一开销对于大量的比较小的 http 消息来说更大。
2优化解决方案
a. 如果我们直接采用 http 连接池,节约了大量的 3 次握手 4 次分手;这样能大大提升吞吐率。
b. feign 的 http 客户端支持 3 种框架;HttpURLConnection、httpclient、okhttp;默认是HttpURLConnection。
c. 传统的 HttpURLConnection 是 JDK 自带的,并不支持连接池,如果要实现连接池的机制,还需要自己来管理连接对象。
对于网络请求这种底层相对复杂的操作,如果有可用的
其他方案,也没有必要自己去管理连接对象。
d. HttpClient 相比传统 JDK 自带的 HttpURLConnection,它封装了访问 http 的请求头,
参数,内容体,响应等等;它不仅使客户端发送 HTTP 请求变得容易,而且也方便了开发人
员测试接口(基于 Http 协议的),即提高了开发的效率,也方便提高代码的健壮性;
另外高并发大量的请求网络的时候,还是用“连接池”提升吞吐量。

在consumer端添加httpClient相关坐标

<!-- 使用Apache HttpClient替换Feign原生httpURLConnection -->
<dependency>
  <groupId>org.apache.httpcomponents</groupId>
  <artifactId>httpclient</artifactId>
</dependency>

3.修改application.properties

#启用 httpclient
feign.httpclient.enabled=true
# feign的最大连接数,我这里理解的连接池中的最大可用连接数量
feign.httpclient.max-connections: 200
# feign单个路径的最大连接数,我这里理解的是一个请求url上的最大并发数
feign.httpclient.max-connections-per-route: 50

工作原理

完成 Feign 调用前所进行的操作:
1. 添加了 Spring Cloud OpenFeign 的依赖
2. 在 SpringBoot 启动类上添加了注解 @EnableFeignCleints
3. 按照 Feign 的规则定义接口 DemoService, 添加@FeignClient 注解
4. 在需要使用 Feign 接口 DemoFeign 的地方, 直接利用@Autowire 进行注入
5. 使用接口完成对服务端的调用

Feign9大主要功能

image.png

1.注解翻译器 Contract feign.Feign

Feign 中可以通过定义 API 接口的方式来调用远程的 Http API,在定义调用 Client 的时候需要增加一些注解来描述这个调用 API 的基本信息,比如请求类型是 GET 还是 POST,请求的 URI 是什么。
Contract 允许用户自定义注解翻译器去解析注解信息。
Contract 决定了哪些注解可以标注在接口/接口方法上是有效的,并且提取出有效的信息,组装成为MethodMetadata元信息。
最典型的应用场景就是在 Spring Cloud 中使用 Feign,我们可以使用 Spring MVC 的注解来定义 Feign 的客户端,就是因为 Spring Cloud OpenFeign 中实现了自己的 SpringMvcContract。

2.Encoder 编码器

将我们的请求信息通过指定的编码方式进行编码到Http请求体中进行传输。

3.QueryMapEncoder 参数查询编码器

QueryMapEncoder 是针对实体类参数查询的编码器,可以基于 QueryMapEncoder 将实体类生成对应的查询参数。

4.Decoder 解码器

Decoder 解码器作用于Response,用于解析Http请求的响应,提取有用信息数据。
Decoder 解码器将HTTP响应feign.Response解码为指定类型的单一对象。

5.ErrorDecoder 错误解码器

ErrorDecoder 错误解码器是在发生错误、异常情况时使用的解码器,允许你对异常进行特殊处理。

6.Logger 日志记录

Feign自己抽象出来的一个日志记录器,负责 Feign 中记录日志的,可以指定 Logger 的级别以及自定义日志的输出。

7.Client 请求执行组件

Client 是负责 HTTP 请求执行的组件,Feign 将请求信息封装好后会交由 Client 来执行。
默认情况下,服务之间调用使用的HttpURLConnection,没有使用连接池,每次使用都会创建HttpURLConnection连接,效率非常低。
为了通过连接池提高效率,我们可以使用appache httpclient做为连接池。当然了配置OkHttpClient连接池,也是类似的方法。

8.Retryer 重试组件

重试并不是报错以后的重试,而是负载均衡客户端发现远程请求实例不可到达后,去重试请求其他实例。
Feign本身也具备重试能力,在早期的Spring Cloud中,Feign使用的是 feign.Retryer.Default#Default() ,重试5次。
但Feign整合了Ribbon,Ribbon也有重试的能力,此时,就可能会导致行为的混乱。
Spring Cloud意识到了此问题,因此做了改进,将Feign的重试改为 feign.Retryer#NEVER_RETRY ,如需使用Feign的重试,只需使用Ribbon的重试配置即可。

9.RequestInterceptor 请求拦截器

我们可以通过实现RequestInterceptor接口的apply方法,在请求执行前设置一些扩展的参数信息或者是修改请求头信息,因为feign在发送请求之前都会调用该接口的apply方法,所以我们也可以通过实现该接口来记录请求发出去的时间点。

流程如下

image.png

SpringBoot 应用启动时, 由针对 @EnableFeignClient 这一注解的处理逻辑触发程序扫描 classPath中所有被@FeignClient 注解的类, 这里以 DemoFeign 为例, 将这些类解析为 BeanDefinition 注册到 Spring 容器中
Sping 容器在为某些用的 Feign 接口的 Bean 注入 DemoFeign 时, Spring 会尝试从容器中查找 DemoFeign 的实现类
由于我们从来没有编写过 DemoFeign 的实现类, 上面步骤获取到的 DemoFeign 的实现类必然是 feign 框架通过扩展 spring 的 Bean 处理逻辑, 为 DemoFeign 创建一个动态接口代理对象, 这里我们将其称为 DemoFeignProxy 注册到spring 容器中。
Spring 最终在使用到 DemoFeign 的 Bean 中注入了 DemoFeignProxy 这一实例。
当业务请求真实发生时, 对于 DemoFeign 的调用被统一转发到了由 Feign 框架实现的 InvocationHandler 中, InvocationHandler 负责将接口中的入参转换为 HTTP 的形式, 发到服务端, 最后再解析 HTTP 响应, 将结果转换为 Java 对象, 予以返回。

简化流程为

我们定义的接口 DemoFeign 由于添加了注解 @FeignClient, 最终产生了一个虚假的实现类代理
使用这个接口的地方, 最终拿到的都是一个假的代理实现类 DemoFeignProxy
所有发生在 DemoFeignProxy 上的调用, 都被转交给 Feign 框架, 翻译成 HTTP 的形式发送出去, 并得到返回结果, 再翻译回接口定义的返回值形式。

工作原理实现细节

SpringApplication.run() --> 调用run方法;准备spring的上下文,完成容器的初始化,创建,加载等。会在不同的时机触发监听器的不同事件。
SpringApplication.refresh() --> 
AbstractApplicationContext.refresh() -->
refresh()方法是spring的核心,在其中完成了容器的初始化 由于这是一个启动方法,它应该销毁已经创建的单例 * 如果失败,以避免悬空资源。换句话说,在调用 * 这个方法之后,要么全部实例化,要么根本不实例化
AbstractApplicationContext.invokeBeanFactoryPostProcessors() -->
调用容器注册的容器级别的后置处理器 传入bean工厂和获取applicationContext中的bean工厂后置处理器(但是由于没有任何实例化过程,所以传递进来的为空)
AbstractApplicationContext.invokeBeanDefinitionRegistryPostProcessors() -->
补充知识点: 上面的 invokeBeanFactoryPostProcessors() 能触发invokeBeanDefinitionRegistryPostProcessors() 是因为 Spring 设计中, BeanDeifinitionRegistryPostProcessor 是 BeanFactoryPostProcessor 的继承
PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors()–>
 特别重要知识点主要用于解析主配置类如扫描加载@Import(FeignClientsRegistrar.class)  可以参考https://blog.csdn.net/yangshangwei/article/details/109060006
ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry()–>
该方法用来注册更多的bean到spring容器中 
ConfigurationClassPostProcessor.processConfigBeanDefinitions()–>
遍历所有的已注册的BeanDefinition,校验是否为配置候选类,判断条件是否添加如下注解处理@ComponentScan@Import注解、@ImportResource等
ConfigurationClassBeanDefinitionReader.loadBeanDefinitions()–>
ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsFromRegistrars -->
解析需要扫描的包路径,最终会交给AutoConfigurationPackages#register去完成,解析完成同样放到beanDefinitionMap(不过此时放的并不是具体的某个Class的BeanDefinition,而是AutoConfigurationPackages对象)
FeignClientsRegistrar.registerBeanDefinitions()

入口注册client @EnableFeignClients开启

先关注对EnableFeignClients 的处理,可以看出它使用了@Import(FeignClientsRegistrar.class),看名字可知是一个注册器,通过扫描某个特性的类,将bean注册到IOC中。Spring 通过调用其 registerBeanDefinitions 方法来获取其提供的 bean definition。

public void registerBeanDefinitions(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {
    //注册configuration
    registerDefaultConfiguration(metadata, registry);
    //注册注解
    registerFeignClients(metadata, registry);
}

private void registerDefaultConfiguration(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {
//获取注解@EnableFeignClients 下设置的属性值
Map<String, Object> defaultAttrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName(), true);

	if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
		String name;
		//判断传入的defaultConfiguration的是不是topClass,所谓topClass就是说此类不是别的类的内部类
		if (metadata.hasEnclosingClass()) {
			name = "default." + metadata.getEnclosingClassName();
		}
		else {
			name = "default." + metadata.getClassName();
		}
		registerClientConfiguration(registry, name,defaultAttrs.get("defaultConfiguration"));
	}
}

private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,Object configuration) {
	//加载FeignClientSpecification bean
	BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(FeignClientSpecification.class);
	builder.addConstructorArgValue(name);
	builder.addConstructorArgValue(configuration);
	//注册
	registry.registerBeanDefinition(name + "." + FeignClientSpecification.class.getSimpleName(),builder.getBeanDefinition());
}

这里会往 Registry 里面添加一个BeanDefinition,即 FeignClientSpecification,configuration是通过 EnableFeignClients 注解的 defaultConfiguration 参数传入。

public void registerFeignClients(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {
    ClassPathScanningCandidateComponentProvider scanner = getScanner();
    scanner.setResourceLoader(this.resourceLoader);

    Set<String> basePackages;

    Map<String, Object> attrs = metadata
        .getAnnotationAttributes(EnableFeignClients.class.getName());
    // 扫描带有FeignClient注解的类
    AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
        FeignClient.class);
    //获取@EnableFeignClients 中clients的值
    final Class<?>[] clients = attrs == null ? null
        : (Class<?>[]) attrs.get("clients");
    if (clients == null || clients.length == 0) {
        //如果没有设置,那么加入要扫描的注解和扫描的包
        scanner.addIncludeFilter(annotationTypeFilter);
        // 确定扫描的包路径列表
        basePackages = getBasePackages(metadata);
    }
    else {
        //如果设置了,最终扫出来的Bean必须是注解中设置的那些
        final Set<String> clientClasses = new HashSet<>();
        basePackages = new HashSet<>();
        for (Class<?> clazz : clients) {
            basePackages.add(ClassUtils.getPackageName(clazz));
            clientClasses.add(clazz.getCanonicalName());
        }
        AbstractClassTestingTypeFilter filter = new AbstractClassTestingTypeFilter() {
            @Override
            protected boolean match(ClassMetadata metadata) {
                String cleaned = metadata.getClassName().replaceAll("\\$", ".");
                return clientClasses.contains(cleaned);
            }
        };
        scanner.addIncludeFilter(
            new AllTypeFilter(Arrays.asList(filter, annotationTypeFilter)));
    }
    //循环扫描,并把根据注解信息,进行相关注册
    for (String basePackage : basePackages) {
        Set<BeanDefinition> candidateComponents = scanner
            .findCandidateComponents(basePackage);
        for (BeanDefinition candidateComponent : candidateComponents) {
                if (candidateComponent instanceof AnnotatedBeanDefinition) {
                    // verify annotated class is an interface
                    AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
                    AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
                    //必须注解在interface上
                    Assert.isTrue(annotationMetadata.isInterface(),
                                  "@FeignClient can only be specified on an interface");
    
                    Map<String, Object> attributes = annotationMetadata
                        .getAnnotationAttributes(
                        FeignClient.class.getCanonicalName());
    
                    String name = getClientName(attributes);
                    registerClientConfiguration(registry, name,attributes.get("configuration"));
    
                    registerFeignClient(registry, annotationMetadata, attributes);
            }
        }
    }
}

private void registerFeignClient(BeanDefinitionRegistry registry,AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
    String className = annotationMetadata.getClassName();
    BeanDefinitionBuilder definition = BeanDefinitionBuilder
        .genericBeanDefinition(FeignClientFactoryBean.class);
    validate(attributes);
    //将属性设置到FeignClientFactoryBean 中
    definition.addPropertyValue("url", getUrl(attributes));
    definition.addPropertyValue("path", getPath(attributes));
    String name = getName(attributes);
    definition.addPropertyValue("name", name);
    definition.addPropertyValue("type", className);
    definition.addPropertyValue("decode404", attributes.get("decode404"));
    definition.addPropertyValue("fallback", attributes.get("fallback"));
    definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));
    //设置Autowire 类型
    definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);

    String alias = name + "FeignClient";
    AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();

    boolean primary = (Boolean)attributes.get("primary"); // has a default, won't be null

    beanDefinition.setPrimary(primary);

    String qualifier = getQualifier(attributes);
    if (StringUtils.hasText(qualifier)) {
        alias = qualifier;
    }
    //注册bean
    BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
                                                           new String[] { alias });
    BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}   


另一个往 Registry 里面添加的 BeanDefinition则是FeignClientFactoryBean,负责注册FeignClient。

也就是说,Feign的注册一共分为一下几步:

扫描@EnableFeignClients注解,如果有defaultConfiguration属性配置,则将configuration注册到BeanDefinition中,如果不指定的话,spring 提供的默认配置是FeignClientsConfiguration。
扫描 basePackage 下面所有包含了 @FeignClient 注解的类
如果@EnableFeignClients中配置了clients属性,则扫描出来的bean只有在clients中配置的那些
循环扫描@FeignClient注解,如果配置了configuration,则将configuration按照 
1 注册打BeanDefinition中,也就是说Feign既支持用作统一的默认的Config作为全局配置,也可以分别在@FeignClient中单独配置configuration 作为局部配置。
将@FeignClient中的其他配置设置到FeignClientFactoryBean中。
最后调用FeignClientFactoryBean#getObject来创建client实例。

加载配置项

FeignClientFactoryBean,Spring Context 创建 Bean 实例时会调用它的 getObject 方法。

public Object getObject() throws Exception {
return getTarget();
}

/**
* @param <T> the target type of the Feign client
* @return a {@link Feign} client created with the specified data and the context information
*/
<T> T getTarget() {
    FeignContext context = applicationContext.getBean(FeignContext.class);
    Feign.Builder builder = feign(context);//1

    if (!StringUtils.hasText(this.url)) {
        //如果没有指定url,获取name值拼接默认url
        String url;
        if (!this.name.startsWith("http")) {
            url = "http://" + this.name;
        }
        else {
            url = this.name;
        }
        url += cleanPath();
        return (T) loadBalance(builder, context, new HardCodedTarget<>(this.type,
                                                                       this.name, url));
    }
    if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
        this.url = "http://" + this.url;
    }
    String url = this.url + cleanPath();
    Client client = getOptional(context, Client.class);
    if (client != null) {
        if (client instanceof LoadBalancerFeignClient) {
            //使用ribbon提供的负载均衡
            // not load balancing because we have a url,
            // but ribbon is on the classpath, so unwrap
            client = ((LoadBalancerFeignClient)client).getDelegate();
        }
        builder.client(client);
    }
    Targeter targeter = get(context, Targeter.class);
    return (T) targeter.target(this, builder, context, new HardCodedTarget<>(
        this.type, this.name, url));
}

如果未指定url,则根据client的name来拼接url,并开启负载均衡如果指定了URL,没有指定client,那么就根据url来调用,相当于直连,没有负载均衡。
如果没有指定client的话,可以使用负载均衡。现在的版本是默认开启负载均衡。

首先在1处可以看出通过feign(context)方法初始化了Feign.Builder,所以着重看一下这个方法:

protected Feign.Builder feign(FeignContext context) {
    //获取FeignClientsConfiguration 中注册的bean ,设置到feign中
    FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
    Logger logger = loggerFactory.create(this.type);

    // @formatter:off
    Feign.Builder builder = get(context, Feign.Builder.class)
        // required values
        .logger(logger)
        .encoder(get(context, Encoder.class))
        .decoder(get(context, Decoder.class))
        .contract(get(context, Contract.class));
    // @formatter:on

    configureFeign(context, builder);

    return builder;
}

这里被设置到builder的bean来自于FeignClientsConfiguration在启动时加载到了context中,这是spring的默认配置,即使在@EnableFeignClients@FeignClient没有配置configuration也能保证可以使用。

@Configuration
public class FeignClientsConfiguration {
    ...
        @Bean
        @ConditionalOnMissingBean
        public Decoder feignDecoder() {
        return new OptionalDecoder(new ResponseEntityDecoder(new SpringDecoder(this.messageConverters)));
    }

    @Bean
    @ConditionalOnMissingBean
    public Encoder feignEncoder() {
        return new SpringEncoder(this.messageConverters);
    }

    @Bean
    @ConditionalOnMissingBean
    public Contract feignContract(ConversionService feignConversionService) {
        return new SpringMvcContract(this.parameterProcessors, feignConversionService);
    }

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

那么自定义的configuration在哪里加载呢,可以看到方法feign(context)中最后一行调用了configureFeign(context, builder),来看一下这个方法。
    protected void configureFeign(FeignContext context, Feign.Builder builder) {
    //获取.properties的属性
    FeignClientProperties properties = applicationContext.getBean(FeignClientProperties.class);
    if (properties != null) {
        if (properties.isDefaultToProperties()) {
            //默认为true
            configureUsingConfiguration(context, builder);
            configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()), builder);
            configureUsingProperties(properties.getConfig().get(this.name), builder);
        } else {
            configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()), builder);
            configureUsingProperties(properties.getConfig().get(this.name), builder);
            configureUsingConfiguration(context, builder);
        }
    } else {
        configureUsingConfiguration(context, builder);
    }
}
//获取用户通过configuration @Bean的自定义配置
protected void configureUsingConfiguration(FeignContext context, Feign.Builder builder) {
    Logger.Level level = getOptional(context, Logger.Level.class);
    if (level != null) {
        builder.logLevel(level);
    }
    Retryer retryer = getOptional(context, Retryer.class);
    if (retryer != null) {
        builder.retryer(retryer);
    }
    ErrorDecoder errorDecoder = getOptional(context, ErrorDecoder.class);
    if (errorDecoder != null) {
        builder.errorDecoder(errorDecoder);
    }
    //connectTimeoutMillis和readTimeoutMillis的默认值
    Request.Options options = getOptional(context, Request.Options.class);
    if (options != null) {
        builder.options(options);
    }
    Map<String, RequestInterceptor> requestInterceptors = context.getInstances(
        this.name, RequestInterceptor.class);
    if (requestInterceptors != null) {
        builder.requestInterceptors(requestInterceptors.values());
    }

    if (decode404) {
        builder.decode404();
    }
}


/***
     *
     * @param config 获取.properties中配置的bean
     * @param builder feign
     */
protected void configureUsingProperties(FeignClientProperties.FeignClientConfiguration config, Feign.Builder builder) {
    if (config == null) {
        return;
    }

    if (config.getLoggerLevel() != null) {
        builder.logLevel(config.getLoggerLevel());
    }
    //设置connectTimeoutMillis和readTimeoutMillis的值,这里的属性值来自于.properties配置的
    if (config.getConnectTimeout() != null && config.getReadTimeout() != null) {
        builder.options(new Request.Options(config.getConnectTimeout(), config.getReadTimeout()));
    }

    if (config.getRetryer() != null) {
        Retryer retryer = getOrInstantiate(config.getRetryer());
        builder.retryer(retryer);
    }

    if (config.getErrorDecoder() != null) {
        ErrorDecoder errorDecoder = getOrInstantiate(config.getErrorDecoder());
        builder.errorDecoder(errorDecoder);
    }

    if (config.getRequestInterceptors() != null && !config.getRequestInterceptors().isEmpty()) {
        // this will add request interceptor to builder, not replace existing
        //这里只会往原有的interceptor中添加新的,而不会删掉原有的。
        for (Class<RequestInterceptor> bean : config.getRequestInterceptors()) {
            RequestInterceptor interceptor = getOrInstantiate(bean);
            builder.requestInterceptor(interceptor);
        }
    }

    if (config.getDecode404() != null) {
        if (config.getDecode404()) {
            builder.decode404();
        }
    }

    if (Objects.nonNull(config.getEncoder())) {
        builder.encoder(getOrInstantiate(config.getEncoder()));
    }

    if (Objects.nonNull(config.getDecoder())) {
        builder.decoder(getOrInstantiate(config.getDecoder()));
    }

    if (Objects.nonNull(config.getContract())) {
        builder.contract(getOrInstantiate(config.getContract()));
    }


把配置文件中的配置项在启动时初始化到FeignClientProperties中。

如果配置文件中没有配置,则将FeignClientsConfiguration中的bean作为默认值设置到builder。
如果配置文件中有配置,并且用默认加载顺序时,首先加载FeignClientsConfiguration中的bean,然后加载在注解中配置的configuration,最后加载配置文件中的。
如果不是默认加载顺序,则首先加载注解中配置的configuration,然后加载配置文件中的配置,最后加载FeignClientsConfiguration中的bean。注意,顺序在后面的配置会覆盖掉前面的

创建client实例

配置文件加载完之后,就是最关键的一步,创建实例.因为两种方式都是通过获取 Targeter 来生成动态代理类。这里拿出了负载均衡做例子。

protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
                            HardCodedTarget<T> target) {
    Client client = getOptional(context, Client.class);
    if (client != null) {
        builder.client(client);
        Targeter targeter = get(context, Targeter.class);
        return targeter.target(this, builder, context, target);
    }

    throw new IllegalStateException(
        "No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-netflix-ribbon?");
}

在 FeignAutoConfiguration 里面,配置了Target,可以看出这里配置了两种相斥的bean。


@Configuration
@ConditionalOnClass(name = "feign.hystrix.HystrixFeign")
protected static class HystrixFeignTargeterConfiguration {
    @Bean
    @ConditionalOnMissingBean
    public Targeter feignTargeter() {
        return new HystrixTargeter();
    }
}

@Configuration
@ConditionalOnMissingClass("feign.hystrix.HystrixFeign")
protected static class DefaultFeignTargeterConfiguration {
    @Bean
    @ConditionalOnMissingBean
    public Targeter feignTargeter() {
        return new DefaultTargeter();
    }
}

如果 feign.hystrix.HystrixFeign`路径不存在,则直接用 FeignBuidler 中DefaultTargeter的 target 方法生成代理。

class HystrixTargeter implements Targeter {

    @Override
    public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,Target.HardCodedTarget<T> target) {
        if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) {
            return feign.target(target);
        }
        feign.hystrix.HystrixFeign.Builder builder = (feign.hystrix.HystrixFeign.Builder) feign;
        SetterFactory setterFactory = getOptional(factory.getName(), context,
                                                  SetterFactory.class);
        if (setterFactory != null) {
            builder.setterFactory(setterFactory);
        }
        Class<?> fallback = factory.getFallback();
        if (fallback != void.class) {
            return targetWithFallback(factory.getName(), context, target, builder, fallback);
        }
        Class<?> fallbackFactory = factory.getFallbackFactory();
        if (fallbackFactory != void.class) {
            return targetWithFallbackFactory(factory.getName(), context, target, builder, fallbackFactory);
        }

        return feign.target(target);
    }
}


class DefaultTargeter implements Targeter {

    @Override
    public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,Target.HardCodedTarget<T> target) {
        return feign.target(target);
    }
}

到这里spring对于创建client实例工作基本完成。接下来主要步骤在feign中。

Feign是怎么工作的 jdk动态代理

public <T> T target(Target<T> target) {
    return build().newInstance(target);
}

private InvocationHandlerFactory invocationHandlerFactory = new InvocationHandlerFactory.Default();

private QueryMapEncoder queryMapEncoder = new QueryMapEncoder.Default();

public Feign build() {
    SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
        new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,
                                             logLevel, decode404, closeAfterDecode);
    //handlersByName将所有参数进行封装,并提供解析接口方法的逻辑
    ParseHandlersByName handlersByName =
        new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder,
                                errorDecoder, synchronousMethodHandlerFactory);
    return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
}

ReflectiveFeign构造函数有三个参数:

ParseHandlersByName 将builder所有参数进行封装,并提供解析接口方法的逻辑
InvocationHandlerFactory 默认值是InvocationHandlerFactory.Default,通过java动态代理的InvocationHandler实现
QueryMapEncoder 接口参数注解@QueryMap时,参数的编码器,默认值QueryMapEncoder.Default
ReflectiveFeign 生成动态代理对象。

ReflectiveFeign#newInstance

public <T> T newInstance(Target<T> target) {
    //为每个方法创建一个SynchronousMethodHandler对象,并放在 Map 里面。
    //targetToHandlersByName是构造器传入的ParseHandlersByName对象,根据target对象生成MethodHandler映射
    Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
    Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
    List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();
	//遍历接口所有方法,构建Method->MethodHandler的映射
    for (Method method : target.type().getMethods()) {
      if (method.getDeclaringClass() == Object.class) {
        continue;
      } else if(Util.isDefault(method)) {
        //如果是 default 方法,说明已经有实现了,用 DefaultHandler接口default方法的Handler
        DefaultMethodHandler handler = new DefaultMethodHandler(method);
        defaultMethodHandlers.add(handler);
        methodToHandler.put(method, handler);
      } else {
        //否则就用上面的 SynchronousMethodHandler
        methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
      }
    }
    // 创建动态代理,factory 是 InvocationHandlerFactory.Default,创建出来的是 ReflectiveFeign.FeignInvocationHanlder,也就是说后续对方法的调用都会进入到该对象的 inovke 方法。
    InvocationHandler handler = factory.create(target, methodToHandler);
    // 创建动态代理对象
    T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(), new Class<?>[]{target.type()}, handler);
	//将default方法直接绑定到动态代理上
    for(DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
      defaultMethodHandler.bindTo(proxy);
    }
    return proxy;
  }
  
这段代码主要的逻辑是:

创建MethodHandler的映射,这里创建的是实现类SynchronousMethodHandler
通过InvocationHandlerFatory创建InvocationHandler
绑定接口的default方法,通过DefaultMethodHandler绑定
SynchronousMethodHandler和DefaultMethodHandler实现了InvocationHandlerFactory.MethodHandler接口,动态代理对象调用方法时,如果是default方法,会直接调用接口方法,因为这里将接口的default方法绑定到动态代理对象上了,其他方法根据方法签名找到SynchronousMethodHandler对象,调用其invoke方法。

创建MethodHandler方法处理器

ReflectiveFeign#apply

public Map<String, MethodHandler> apply(Target key) {
    //通过contract解析接口方法,生成MethodMetadata列表,默认的contract解析Feign自定义的http注解
    List<MethodMetadata> metadata =contract.parseAndValidatateMetadata(key.type());
    Map<String, MethodHandler> result = new LinkedHashMap<String, MethodHandler>();
    for (MethodMetadata md : metadata) {
        //根据目标接口类和方法上的注解信息判断该用哪种 buildTemplate
        //BuildTemplateByResolvingArgs实现RequestTemplate.Factory,RequestTemplate的工厂
        BuildTemplateByResolvingArgs buildTemplate;
        if (!md.formParams().isEmpty() && md.template().bodyTemplate() == null) {
            //如果有formParam,并且bodyTemplate不为空,请求体为x-www-form-urlencoded格式
            //将会解析form参数,填充到bodyTemplate中
            buildTemplate = new BuildFormEncodedTemplateFromArgs(md, encoder);
        } else if (md.bodyIndex() != null) {
            //如果包含请求体,将会用encoder编码请求体对象
            buildTemplate = new BuildEncodedTemplateFromArgs(md, encoder);
        } else {
            //默认的RequestTemplate的工厂,没有请求体,不需要编码器
            buildTemplate = new BuildTemplateByResolvingArgs(md);
        }
        //使用工厂SynchronousMethodHandler.Factory创建SynchronousMethodHandler
        result.put(md.configKey(),
                   factory.create(key, md, buildTemplate, options, decoder, errorDecoder));
    }
    return result;
}

这段代码的逻辑是:

通过Contract解析接口方法,生成MethodMetadata,默认的Contract解析Feign自定义的http注解

根据MethodMetadata方法元数据生成特定的RequestTemplate的工厂

使用SynchronousMethodHandler.Factory工厂创建SynchronousMethodHandler
这里有两个工厂不要搞混淆了,SynchronousMethodHandler工厂和RequestTemplate工厂,SynchronousMethodHandler的属性包含RequestTemplate工厂

SynchronousMethodHandlerFactory#create
public MethodHandler create(Target<?> target, MethodMetadata md,
                        RequestTemplate.Factory buildTemplateFromArgs,
                        Options options, Decoder decoder, ErrorDecoder errorDecoder) {
    //buildTemplateFromArgs--RequestTemplate Facoory
    return new SynchronousMethodHandler(target, client, retryer, requestInterceptors, logger,
                                        logLevel, md, buildTemplateFromArgs, options, decoder,
                                        errorDecoder, decode404);
}

Contract解析

Contract 允许用户自定义注解翻译器去解析注解信息。与SpringMvcContract类似

这里有两个Contract,一个是feign默认的Contract,另一个是Spring实现的Contract。
Feign.Contract-Contract.Default
feign默认的解析器是Contract.Default继承了Contract.BaseContract,解析生成MethodMetadata方法入口:

Contract解析接口方法生成MethodMetadata

@Override
public List<MethodMetadata> parseAndValidatateMetadata(Class<?> targetType) {

    Map<String, MethodMetadata> result = new LinkedHashMap<String, MethodMetadata>();
    for (Method method : targetType.getMethods()) {
        //...
        MethodMetadata metadata = parseAndValidateMetadata(targetType, method);
        //...
        result.put(metadata.configKey(), metadata);
    }
    return new ArrayList<MethodMetadata>(result.values());
}

protected MethodMetadata parseAndValidateMetadata(Class<?> targetType, Method method) {
    MethodMetadata data = new MethodMetadata();
    data.returnType(Types.resolve(targetType, targetType, method.getGenericReturnType()));
    data.configKey(Feign.configKey(targetType, method));

    if(targetType.getInterfaces().length == 1) {
        processAnnotationOnClass(data, targetType.getInterfaces()[0]);
    }
    //处理Class上的注解
    processAnnotationOnClass(data, targetType);

    for (Annotation methodAnnotation : method.getAnnotations()) {
        //处理方法注解
        processAnnotationOnMethod(data, methodAnnotation, method);
    }
    //...
    Class<?>[] parameterTypes = method.getParameterTypes();
    Type[] genericParameterTypes = method.getGenericParameterTypes();
    
    Annotation[][] parameterAnnotations = method.getParameterAnnotations();
    int count = parameterAnnotations.length;
    for (int i = 0; i < count; i++) {
        boolean isHttpAnnotation = false;
        if (parameterAnnotations[i] != null) {
            //方法参数注解
            isHttpAnnotation = processAnnotationsOnParameter(data, parameterAnnotations[i], i);
        }
        if (parameterTypes[i] == URI.class) {
            //参数类型是URI,后面构造http请求时,使用该URI
            data.urlIndex(i);
        } else if (!isHttpAnnotation) {
            //如果没有被http注解,就是body参数
            //...
            data.bodyIndex(i);
            data.bodyType(Types.resolve(targetType, targetType, genericParameterTypes[i]));
        }
    }

    if (data.headerMapIndex() != null) {
        //@HeaderMap注解的参数必须是Map,key类型必须是String
        checkMapString("HeaderMap", parameterTypes[data.headerMapIndex()], genericParameterTypes[data.headerMapIndex()]);
    }

    if (data.queryMapIndex() != null) {
        if (Map.class.isAssignableFrom(parameterTypes[data.queryMapIndex()])) {
            //@QueryMap注解的参数如果是Map,key类型必须是String
            checkMapKeys("QueryMap", genericParameterTypes[data.queryMapIndex()]);
        }
    }
    return data;
}

1.处理Class上的注解

这里主要处理@Headers

//处理@Headers注解
protected void processAnnotationOnClass(MethodMetadata data, Class<?> targetType) {
    if (targetType.isAnnotationPresent(Headers.class)) {
        //被Headers注解
        String[] headersOnType = targetType.getAnnotation(Headers.class).value();
        //...
        //header解析成map,加到MethodMetadata中
        Map<String, Collection<String>> headers = toMap(headersOnType);
        headers.putAll(data.template().headers());
        data.template().headers(null); // to clear
        data.template().headers(headers);
    }
}

2.处理方法上的注解

这里主要处理@RequestLine@Body@Headers

//处理方法注解
protected void processAnnotationOnMethod(MethodMetadata data, Annotation methodAnnotation,
                                         Method method) {
    Class<? extends Annotation> annotationType = methodAnnotation.annotationType();
    if (annotationType == RequestLine.class) {
        //@RequestLine注解
        String requestLine = RequestLine.class.cast(methodAnnotation).value();
        //...
        if (requestLine.indexOf(' ') == -1) {
            //...
            data.template().method(requestLine);
            return;
        }
        //http请求方法
        data.template().method(requestLine.substring(0, requestLine.indexOf(' ')));
        if (requestLine.indexOf(' ') == requestLine.lastIndexOf(' ')) {
            // no HTTP version is ok
            data.template().append(requestLine.substring(requestLine.indexOf(' ') + 1));
        } else {
            // skip HTTP version
            data.template().append(
                requestLine.substring(requestLine.indexOf(' ') + 1, requestLine.lastIndexOf(' ')));
        }
        //将'%2F'反转为'/'
        data.template().decodeSlash(RequestLine.class.cast(methodAnnotation).decodeSlash());
        //参数集合格式化方式,默认使用key=value0&key=value1
        data.template().collectionFormat(RequestLine.class.cast(methodAnnotation).collectionFormat());

    } else if (annotationType == Body.class) {
        //@Body注解
        String body = Body.class.cast(methodAnnotation).value();
        //...
        if (body.indexOf('{') == -1) {
            //body中不存在{,直接传入body
            data.template().body(body);
        } else {
            //body中存在{,就是bodyTemplate方式
            data.template().bodyTemplate(body);
        }
    } else if (annotationType == Headers.class) {
        //@Header注解
        String[] headersOnMethod = Headers.class.cast(methodAnnotation).value();
        //...
        data.template().headers(toMap(headersOnMethod));
    }
}

3.处理参数上的注解

这里主要处理@Param、@QueryMap、@HeaderMap,只要有这三个注解,则isHttpAnnotation=true。

//处理参数上的注解
protected boolean processAnnotationsOnParameter(MethodMetadata data, Annotation[] annotations, int paramIndex) {
    boolean isHttpAnnotation = false;
    for (Annotation annotation : annotations) {
        Class<? extends Annotation> annotationType = annotation.annotationType();
        if (annotationType == Param.class) {
            //@Param注解
            Param paramAnnotation = (Param) annotation;
            String name = paramAnnotation.value();
            //...
            //增加到MethodMetadata中
            nameParam(data, name, paramIndex);
            //@Param注解的expander参数,定义参数的解释器,默认是ToStringExpander,调用参数的toString方法
            Class<? extends Param.Expander> expander = paramAnnotation.expander();
            if (expander != Param.ToStringExpander.class) {
                data.indexToExpanderClass().put(paramIndex, expander);
            }
            //参数是否已经urlEncoded,如果没有,会使用urlEncoded方式编码
            data.indexToEncoded().put(paramIndex, paramAnnotation.encoded());
            isHttpAnnotation = true;
            String varName = '{' + name + '}';
            if (!data.template().url().contains(varName) &&
                !searchMapValuesContainsSubstring(data.template().queries(), varName) &&
                !searchMapValuesContainsSubstring(data.template().headers(), varName)) {
                //如果参数不在path里面,不在query里面,不在header里面,就设置到formParam中
                data.formParams().add(name);
            }
        } else if (annotationType == QueryMap.class) {
            //@QueryMap注解,注解参数对象时,将该参数转换为http请求参数格式发送
            //...
            data.queryMapIndex(paramIndex);
            data.queryMapEncoded(QueryMap.class.cast(annotation).encoded());
            isHttpAnnotation = true;
        } else if (annotationType == HeaderMap.class) {
            //@HeaderMap注解,注解一个Map类型的参数,放入http header中发送
            //...
            data.headerMapIndex(paramIndex);
            isHttpAnnotation = true;
        }
    }
    return isHttpAnnotation;
}

Spring.Contract-SpringMvcContract 解析生成MethodMetadata方法入口

feign默认的解析器是SpringMvcContract继承了Contract.BaseContract

@Override
public MethodMetadata parseAndValidateMetadata(Class<?> targetType, Method method) {
    this.processedMethods.put(Feign.configKey(targetType, method), method);
    MethodMetadata md = super.parseAndValidateMetadata(targetType, method);

    RequestMapping classAnnotation = findMergedAnnotation(targetType,
                                                          RequestMapping.class);
    if (classAnnotation != null) {
        // produces - use from class annotation only if method has not specified this
        // 如果Accept为空,则设置@RequestMapping的produces
        if (!md.template().headers().containsKey(ACCEPT)) {
            parseProduces(md, method, classAnnotation);
        }

        // consumes -- use from class annotation only if method has not specified this
        // 如果Content-Type为空,则设置@RequestMapping的consumes
        if (!md.template().headers().containsKey(CONTENT_TYPE)) {
            parseConsumes(md, method, classAnnotation);
        }

        // headers -- class annotation is inherited to methods, always write these if
        // present 设置heerders
        parseHeaders(md, method, classAnnotation);
    }
    return md;
}

这里parseAndValidateMetadata调用了父类即BaseContract的方法,如上,也会分为3部来处理,这里只分析有区别的部分。解析完成之后,会根据在class上注解的@RequestMapping的属性值来设置到MethodMetadata中。

1.处理Class上的注解

这里主要处理@RequestMapping

protected void processAnnotationOnClass(MethodMetadata data, Class<?> clz) {
    if (clz.getInterfaces().length == 0) {
        RequestMapping classAnnotation = findMergedAnnotation(clz,
                                                              RequestMapping.class);
        if (classAnnotation != null) {
            // Prepend path from class annotation if specified
            if (classAnnotation.value().length > 0) {
                String pathValue = emptyToNull(classAnnotation.value()[0]);
                pathValue = resolve(pathValue);
                if (!pathValue.startsWith("/")) {
                    pathValue = "/" + pathValue;
                }
                //插入url
                data.template().insert(0, pathValue);
            }
        }
    }
}

2.处理方法上的注解

这里主要处理@RequestMapping,和处理class时差不多

protected void processAnnotationOnMethod(MethodMetadata data,
                                     Annotation methodAnnotation, Method method) {
if (!RequestMapping.class.isInstance(methodAnnotation) && !methodAnnotation
    .annotationType().isAnnotationPresent(RequestMapping.class)) {
    return;
}

RequestMapping methodMapping = findMergedAnnotation(method, RequestMapping.class);
// 设置HTTP Method
RequestMethod[] methods = methodMapping.method();
if (methods.length == 0) {
    methods = new RequestMethod[] { RequestMethod.GET };
}
checkOne(method, methods, "method");
data.template().method(methods[0].name());

// path
checkAtMostOne(method, methodMapping.value(), "value");
if (methodMapping.value().length > 0) {
    String pathValue = emptyToNull(methodMapping.value()[0]);
    if (pathValue != null) {
        pathValue = resolve(pathValue);
        // Append path from @RequestMapping if value is present on method
        if (!pathValue.startsWith("/")
            && !data.template().toString().endsWith("/")) {
            pathValue = "/" + pathValue;
        }
        data.template().append(pathValue);
    }
}

// produces
parseProduces(data, method, methodMapping);

// consumes
parseConsumes(data, method, methodMapping);

// headers
parseHeaders(data, method, methodMapping);

data.indexToExpander(new LinkedHashMap<Integer, Param.Expander>());

3.处理参数上的注解

这里主要处理@Param@QueryMap@HeaderMap

protected boolean processAnnotationsOnParameter(MethodMetadata data,
                                            Annotation[] annotations, int paramIndex) {
    boolean isHttpAnnotation = false;

    AnnotatedParameterProcessor.AnnotatedParameterContext context = new SimpleAnnotatedParameterContext(
        data, paramIndex);
    Method method = this.processedMethods.get(data.configKey());
    for (Annotation parameterAnnotation : annotations) {
        AnnotatedParameterProcessor processor = this.annotatedArgumentProcessors
            .get(parameterAnnotation.annotationType());
        if (processor != null) {
            Annotation processParameterAnnotation;
            // synthesize, handling @AliasFor, while falling back to parameter name on
            // missing String #value():
            processParameterAnnotation = synthesizeWithMethodParameterNameAsFallbackValue(
                parameterAnnotation, method, paramIndex);
            //调用不同策略处理不同的注解
            isHttpAnnotation |= processor.processArgument(context,
                                                          processParameterAnnotation, method);
        }
    }
    if (isHttpAnnotation && data.indexToExpander().get(paramIndex) == null
        && this.conversionService.canConvert(
            method.getParameterTypes()[paramIndex], String.class)) {
        data.indexToExpander().put(paramIndex, this.expander);
    }
    return isHttpAnnotation;
}

这里spring提供了三种参数上的注解解析

RequestHeaderParameterProcessor:处理@RequestHeader
PathVariableParameterProcessor:处理@PathVariable
RequestParamParameterProcessor:处理@RequestParam
代码稍微有点多,但是逻辑很清晰,先处理类上的注解,再处理方法上注解,最后处理方法参数注解,把所有注解的情况都处理到就可以了。

生成的MethodMetadata的结构如下:
public final class MethodMetadata implements Serializable {
//标识方法的key,接口名加方法签名:GitHub#contributors(String,String)
private String configKey;
//方法返回值类型
private transient Type returnType;
//uri参数的位置,方法中可以写个uri参数,发请求时直接使用这个参数
private Integer urlIndex;
//body参数的位置,只能有一个注解的参数为body,否则报错
private Integer bodyIndex;
//headerMap参数的位置
private Integer headerMapIndex;
//@QueryMap注解参数位置
private Integer queryMapIndex;
//@QueryMap注解里面encode参数,是否已经urlEncode编码过了
private boolean queryMapEncoded;
//body的类型
private transient Type bodyType;
//RequestTemplate
private RequestTemplate template = new RequestTemplate();
//form请求参数
private List<String> formParams = new ArrayList<String>();
//方法参数位置和名称的map
private Map<Integer, Collection<String>> indexToName ;
//@Param中注解的expander方法,可以指定解析参数类
private Map<Integer, Class<? extends Expander>> indexToExpanderClass ;
//参数是否被urlEncode编码过了,@Param中encoded方法
private Map<Integer, Boolean> indexToEncoded ;
//自定义的Expander
private transient Map<Integer, Expander> indexToExpander;

调用client服务 客户端发送请求走的方法

ReflectiveFeign.FeignInvocationHanlder 的 invoke 方法

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    //通过动态代理实现了几个通用方法,比如 equals、toString、hasCode
    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();
    }
    //找到具体的 method 的 Handler,然后调用 invoke 方法。这样就又进入了SynchronousMethodHandler对象的 invoke 方法。
    return dispatch.get(method).invoke(args);
}


SynchronousMethodHandler 的 invoke 方法主要是应用 encoder,decoder 以及 retry 等配置, 并执行http请求及返回结果的处理

public Object invoke(Object[] argv) throws Throwable {
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;
    }
}

}

Object executeAndDecode(RequestTemplate template) throws Throwable { //通过RequestTemplate生成Request,这里会首先执行RequestInterceptors Request request = targetRequest(template);

if (logLevel != Logger.Level.NONE) {
    logger.logRequest(metadata.configKey(), logLevel, request);
}

Response response;
long start = System.nanoTime();
try {
    //通过 client 获得请求的返回值
    response = client.execute(request, options);
    // ensure the request is set. TODO: remove in Feign 10
    response.toBuilder().request(request).build();
} catch (IOException e) {
    if (logLevel != Logger.Level.NONE) {
        logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime(start));
    }
    throw errorExecuting(request, e);
}
long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);

boolean shouldClose = true;
try {
    if (logLevel != Logger.Level.NONE) {
        response =
            logger.logAndRebufferResponse(metadata.configKey(), logLevel, response, elapsedTime);
        // ensure the request is set. TODO: remove in Feign 10
        //将request设置到response中
        response.toBuilder().request(request).build();
    }
    if (Response.class == metadata.returnType()) {
        if (response.body() == null) {
            return response;
        }
        if (response.body().length() == null ||
            response.body().length() > MAX_RESPONSE_BUFFER_SIZE) {
            shouldClose = false;
            return response;
        }
        // Ensure the response body is disconnected
        byte[] bodyData = Util.toByteArray(response.body().asInputStream());
        return response.toBuilder().body(bodyData).build();
    }
    if (response.status() >= 200 && response.status() < 300) {
        if (void.class == metadata.returnType()) {
            return null;
        } else {
            //解码
            return decode(response);
        }
    } else if (decode404 && response.status() == 404 && void.class != metadata.returnType()) {
        return decode(response);
    } else {
        throw errorDecoder.decode(metadata.configKey(), response);
    }
} 
//...



Client也有两种,一种直连,一种负载均衡,这里主要分析直连,负载均衡最后也要调用这里。

Client.Default#execute

调用HttpURLConnection进行http请求

public Response execute(Request request, Options options) throws IOException {
    HttpURLConnection connection = convertAndSend(request, options);
    return convertResponse(connection).toBuilder().request(request).build();
}

HttpURLConnection convertAndSend(Request request, Options options) throws IOException {
    final HttpURLConnection
        connection =
        (HttpURLConnection) new URL(request.url()).openConnection();
    if (connection instanceof HttpsURLConnection) {
        HttpsURLConnection sslCon = (HttpsURLConnection) connection;
        if (sslContextFactory != null) {
            sslCon.setSSLSocketFactory(sslContextFactory);
        }
        if (hostnameVerifier != null) {
            sslCon.setHostnameVerifier(hostnameVerifier);
        }
    }
    connection.setConnectTimeout(options.connectTimeoutMillis());
    connection.setReadTimeout(options.readTimeoutMillis());
    connection.setAllowUserInteraction(false);
    connection.setInstanceFollowRedirects(options.isFollowRedirects());
    connection.setRequestMethod(request.method());

    Collection<String> contentEncodingValues = request.headers().get(CONTENT_ENCODING);
    boolean
        gzipEncodedRequest =
        contentEncodingValues != null && contentEncodingValues.contains(ENCODING_GZIP);
    boolean
        deflateEncodedRequest =
        contentEncodingValues != null && contentEncodingValues.contains(ENCODING_DEFLATE);

    boolean hasAcceptHeader = false;
    Integer contentLength = null;
    for (String field : request.headers().keySet()) {
        if (field.equalsIgnoreCase("Accept")) {
            hasAcceptHeader = true;
        }
        for (String value : request.headers().get(field)) {
            if (field.equals(CONTENT_LENGTH)) {
                if (!gzipEncodedRequest && !deflateEncodedRequest) {
                    contentLength = Integer.valueOf(value);
                    connection.addRequestProperty(field, value);
                }
            } else {
                connection.addRequestProperty(field, value);
            }
        }
    }
    // Some servers choke on the default accept string.
    if (!hasAcceptHeader) {
        connection.addRequestProperty("Accept", "*/*");
    }

    if (request.body() != null) {
        if (contentLength != null) {
            connection.setFixedLengthStreamingMode(contentLength);
        } else {
            connection.setChunkedStreamingMode(8196);
        }
        connection.setDoOutput(true);
        OutputStream out = connection.getOutputStream();
        if (gzipEncodedRequest) {
            out = new GZIPOutputStream(out);
        } else if (deflateEncodedRequest) {
            out = new DeflaterOutputStream(out);
        }
        try {
            out.write(request.body());
        } finally {
            try {
                out.close();
            } catch (IOException suppressed) { // NOPMD
            }
        }
    }
    return connection;
}

Response convertResponse(HttpURLConnection connection) throws IOException {
    int status = connection.getResponseCode();
    String reason = connection.getResponseMessage();

    if (status < 0) {
        throw new IOException(format("Invalid status(%s) executing %s %s", status,
                                     connection.getRequestMethod(), connection.getURL()));
    }

    Map<String, Collection<String>> headers = new LinkedHashMap<String, Collection<String>>();
    for (Map.Entry<String, List<String>> field : connection.getHeaderFields().entrySet()) {
        // response message
        if (field.getKey() != null) {
            headers.put(field.getKey(), field.getValue());
        }
    }

    Integer length = connection.getContentLength();
    if (length == -1) {
        length = null;
    }
    InputStream stream;
    if (status >= 400) {
        stream = connection.getErrorStream();
    } else {
        stream = connection.getInputStream();
    }
    return Response.builder()
        .status(status)
        .reason(reason)
        .headers(headers)
        .body(stream, length)
        .build();
}