Feign底层默认是JDK自带的HttpURLConnection,它是单线程发送HTTP请求的,不能配置线程池,我们使用
Ckhttp或者HttpClient来发送http请求,并且它们两个都支持线程池。

1、在 Spring 项目启动阶段,服务 A 的OpenFeign 框架会发起一个主动的扫包流程。
2、从指定的目录下扫描并加载所有被 @FeignClient 注解修饰的接口,然后将这些接口转换成 Bean,统一交给 Spring 来管理。
3、根据这些接口会经过 MVC Contract 协议解析,将方法上的注解都解析出来,放到 MethodMetadata 元数据中。
4、基于上面加载的每一个 FeignClient 接口,会生成一个动态代理对象,指向了一个包含对应方法的 MethodHandler 的 HashMap。MethodHandler 对元数据有引用关系。生成的动态代理对象会被添加到 Spring 容器中,并注入到对应的服务里。
5、服务 A 调用接口,准备发起远程调用。
6、从动态代理对象 Proxy 中找到一个 MethodHandler 实例,生成 Request,包含有服务的请求 URL(不包含服务的 IP)。
7、经过负载均衡算法找到一个服务的 IP 地址,拼接出请求的 URL
8、服务 B 处理服务 A 发起的远程调用请求,执行业务逻辑后,返回响应给服务 A。

(1)@EnableFeignClients 这个注解使用 Spring 框架的 Import 注解导入了 FeignClientsRegistrar 类,开始了 OpenFeign 组件的加载。PassJava 示例代码如下所示。

1
2
3
4
5
6
7
8
// 启动类加上这个注解 
@EnableFeignClients(basePackages = "com.jackson0714.passjava.member.feign")

// EnableFeignClients 类还引入了 FeignClientsRegistrar 类
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
...
}

(2)FeignClientsRegistrar 负责 Feign 接口的加载。

源码如下所示:

1
2
3
4
5
6
7
8
@Override 
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
// 注册配置
registerDefaultConfiguration(metadata, registry);
// 注册 FeignClient
registerFeignClients(metadata, registry);
}

(3)registerFeignClients 会扫描指定包。

核心源码如下,调用 find 方法来查找指定路径 basePackage 的所有带有 @FeignClients 注解的带有 @FeignClient 注解的类、接口。

1
2
Set<BeanDefinition> candidateComponents = scanner 
.findCandidateComponents(basePackage);

(4)只保留带有 @FeignClient 的接口。

1
2
3
4
5
6
7
8
// 判断是否是带有注解的 Bean。 
if (candidateComponent instanceof AnnotatedBeanDefinition) {
// 判断是否是接口
AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
// @FeignClient 只能指定在接口上。
Assert.isTrue(annotationMetadata.isInterface(),
"@FeignClient can only be specified on an interface");

在创建 FeignClient Bean 的过程中就会去生成动态代理对象。调用接口时,其实就是调用动态代理对象的方法来发起请求的。

分析动态代理的入口方法为 getObject()。源码如下所示:

1
2
3
Targeter targeter = get(context, Targeter.class); 
return (T) targeter.target(this, builder, context,
new HardCodedTarget<>(this.type, this.name, url));

接着调用 target 方法这一块,里面的代码真的很多很细,我把核心的代码拿出来给大家讲下,这个 target 会有两种实现类:

DefaultTargeter 和 HystrixTargeter。而不论是哪种 target,都需要去调用 Feign.java 的 builder 方法去构造一个 feign client。

在构造的过程中,依赖 ReflectiveFeign 去构造。源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
// 省略部分代码 
public class ReflectiveFeign extends Feign {
// 为 feign client 接口中的每个接口方法创建一个 methodHandler
public <T> T newInstance(Target<T> target) {
for(...) {
methodToHandler.put(method, handler);
}
// 基于 JDK 动态代理的机制,创建了一个 passjava-study 接口的动态代理,所有对接口的调用都会被拦截,然后转交给 handler 的方法。
InvocationHandler handler = factory.create(target, methodToHandler);
T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
new Class<?>[] {target.type()}, handler);
}

ReflectiveFeign 做的工作就是为带有 @FeignClient 注解的接口,创建出接口方法的动态代理对象。

比如示例代码中的接口 StudyTimeFeignService,会给这个接口中的方法 getMemberStudyTimeList 创建一个动态代理对象。

1
2
3
4
5
@FeignClient("passjava-study") 
public interface StudyTimeFeignService {
@RequestMapping("study/studytime/member/list/test/{id}")
public R getMemberStudyTimeList(@PathVariable("id") Long id);
}

创建动态代理的原理图如下所示:

img

解析 FeignClient 接口上各个方法级别的注解,比如远程接口的 URL、接口类型(Get、Post 等)、各个请求参数等。这里用到了 MVC Contract 协议解析,后面会讲到。

  • 然后将解析到的数据封装成元数据,并为每一个方法生成一个对应的 MethodHandler 类作为方法级别的代理。相当于把服务的请求地址、接口类型等都帮我们封装好了。这些 MethodHandler 方法会放到一个 HashMap 中。
  • 然后会生成一个 InvocationHandler 用来管理这个 hashMap,其中 Dispatch 指向这个 HashMap。
  • 然后使用 Java 的 JDK 原生的动态代理,实现了 FeignClient 接口的动态代理 Proxy 对象。这个 Proxy 会添加到 Spring 容器中。
  • 当要调用接口方法时,其实会调用动态代理 Proxy 对象的 methodHandler 来发送请求。

这个动态代理对象的结构如下所示,它包含了所有接口方法的 MethodHandler。

img