OpenFeign
核心流程
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。
OpenFeign 包扫描原理
@EnableFeignClients 这个注解使用 Spring 框架的 Import 注解导入了 FeignClientsRegistrar 类
// 启动类加上这个注解
@EnableFeignClients(basePackages = "com.test.feign")// EnableFeignClients 类还引入了 FeignClientsRegistrar 类
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {...
}
FeignClientsRegistrar 负责 Feign 接口的加载。
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {// 注册配置registerDefaultConfiguration(metadata, registry);// 注册 FeignClientregisterFeignClients(metadata, registry);
}
registerFeignClients 会扫描指定包。
Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents(basePackage);
只保留带有 @FeignClient 的接口。
// 判断是否是带有注解的 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 到 Spring 的原理
- 解析 @FeignClient 定义的属性。
- 将注解@FeignClient 的属性 + 接口 StudyTimeFeignService的信息构造成一个 StudyTimeFeignService 的 beanDefinition。
- 然后将 beanDefinition 转换成一个 holder,这个 holder 就是包含了 beanDefinition, alias, beanName 信息。
- 最后将这个 holder 注册到 Spring 容器中。
OpenFeign 动态代理原理
// 省略部分代码
public class ReflectiveFeign extends Feign {// 为 feign client 接口中的每个接口方法创建一个 methodHandlerpublic <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 注解的接口,创建出接口方法的动态代理对象。
- 解析 FeignClient 接口上各个方法级别的注解,比如远程接口的 URL、接口类型(Get、Post 等)、各个请求参数等。这里用到了 MVC Contract 协议解析,后面会讲到。
- 然后将解析到的数据封装成元数据,并为每一个方法生成一个对应的 MethodHandler 类作为方法级别的代理。相当于把服务的请求地址、接口类型等都帮我们封装好了。这些 MethodHandler 方法会放到一个 HashMap 中。
- 然后会生成一个 InvocationHandler 用来管理这个 hashMap,其中 Dispatch 指向这个 HashMap。
- 然后使用 Java 的 JDK 原生的动态代理,实现了 FeignClient 接口的动态代理 Proxy 对象。这个 Proxy 会添加到 Spring 容器中。
- 当要调用接口方法时,其实会调用动态代理 Proxy 对象的 methodHandler 来发送请求。
OpenFeign 如何与 Ribbon 整合的原理
- 根据服务名从缓存中找 FeignLoadBalancer,如果缓存中没有,则创建一个 FeignLoadBalancer。
- FeignLoadBalancer 会创建出一个 command,这个 command 会执行一个 sumbit 方法。
- submit 方法里面就会用 Ribbon 的负载均衡算法选择一个 server。
- 然后将 IP 地址和之前剔除掉服务名称的 URL 进行拼接,生成最后的服务地址。
- 最后 FeignLoadBalancer 执行 execute 方法发送请求。
Ribbon 的核心组件 ServerListUpdater,用来同步注册表的,它有一个实现类 PollingServerListUpdater ,专门用来做定时同步的。默认1s 后执行一个 Runnable 线程,后面就是每隔 30s 执行 Runnable 线程。这个 Runnable 线程就是去获取注册中心的注册表的。
OpenFeign 处理响应的原理
这个里面做的事情就是调用 ResponseEntityDecoder 的 decode 方法,将 Json 字符串转化为 Bean 对象。
说明
参考链接