目录
Feign的说明
简单集成方式
调用方式
OpenFeign调用原理
OpenFeign 包扫描原理
注册 FeignClient 到 Spring 的原理
OpenFeign 动态代理原理
对SpringMVC注解的解析
OpenFeign 发送请求的原理
FeignClientFactoryBean做了哪些事?
ReflectiveFeign做了哪些事?
为OpenFeign增加简单负载均衡
Feign的说明
Feign的英文释义:假装,装作,佯装
Feign 作用是来简化我们发起远程调用的代码的,简化成像调用本地方法那样。
Feign 和 OpenFeign 有很多大同小异之处,不同的是 OpenFeign 支持 MVC 注解。
简单总结下 OpenFeign 能用来做什么:
- OpenFeign 是声明式的 HTTP 客户端,让远程调用更简单。
- 提供了HTTP请求的模板(RestTemplate),编写简单的接口和插入注解,就可以定义好HTTP请求的参数、格式、地址等信息
- 可以整合负载均衡组件(Ribbon、LoadBalancer)和 服务熔断(Hystix、Sentinel),不需要显示调用
- Spring Cloud Feign 在 Netflix Feign的基础上扩展了对SpringMVC注解的支持
简单集成方式
Pom加入依赖
应用程序加入注解
配置接口
配置文件增加调用地址
使用远程接口
调用方式
整体调用时序图
OpenFeign调用原理
初始化过程
调用过程
关键对象:
FeignClientFactoryBean.getObject,用于对接Spring,关联@FeignClient注解
ReflectiveFeign.newInstance 生成代理对象
HardCodedTarget 存储接口信息以及FeignClient信息
Client接口 远程调用执行工具
OpenFeign 包扫描原理
1) 声明Feign调用接口时,不用加@Component 注解,因为在Application那里已经加了EnableFeignClients。在Application上加了EnableFeignClients注解,会使用 Spring 框架的 Import 注解导入FeignClientsRegistrar 类,自动检查@FeignClient注解
2) FeignClientsRegistrar 负责 Feign 接口的加载。
由于FeignClientsRegistrar 实现了ImportBeanDefinitionRegistrar接口的registerBeanDefinitions方法,所以,可以由ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry主动调起
3) registerFeignClients 会对指定路径执行包扫描
通过getBasePackages方法,从metadata里获取EnableFeignClients注解里的包扫描参数,存入basePackages集合中,可能是多个。通过路径扫描,在basePackage路径下,找到所有的备选组件将所有备选组件存入candidateComponents集合
4) 只保留带有 @FeignClient 的接口。
5) 循环将所有@FeignClient注释的接口注册到Spring中
代码示例:
注册 FeignClient 到 Spring 的原理
还是在 registerFeignClients 方法中,当 FeignClient 扫描完后,就要为这些 FeignClient 接口生成一个动态代理对象。
registerFeignClient调用eagerlyRegisterFeignClientBeanDefinition方法注册接口
首先,生成一个基于FeignClientFactoryBean的Bean定义
根据类的名字我们可以知道这是一个工厂Bean,用来创建 FeignClient Bean 的。
我们最开始用的 @FeignClient,里面有个参数 "remoteSysService",这个是注解的属性,当 OpenFeign 框架去创建 FeignClient Bean 的时候,就会使用这些参数去生成 Bean。
RegisterFeignClientBeanDefinition的注册步骤如下:
1)创建一个class是FeignClientFactoryBean的beanDefinition 对象;
2)解析@FeignClient注解定义接口的各个属性,将各个属性设置到beanDefinition 中。如url、path、name、contextId(就是我们定义的远程服务名称)、fallbackFactory等;
3)然后将 beanDefinition 转换成一个 BeanDefinitionHolder,这个 holder 就是包含了 beanDefinition, alias, beanName 信息。
4)最后将这个 holder 注册到 Spring 容器中。
Registry就是DefaultListableBeanFactory,他将RemoteSystemService注册到容器中,
需要注意的是beanDefinition里存的class不是RemoteSystemService,而是FeignClientFactoryBean,他做为一个工厂Bean为后续调用提供Bean的创建。
至此,FeignClient注册的初始化已经完成。
小节:注册到Spring容器之后,服务要调用接口的时候,就可以直接用 FeignClient 的接口方法了
返回结果如果使用集成HashMap的方式,可以在不定义Provider的实体对象即可以使用。
那么这个服务以及方法是如何被调起的呢?
OpenFeign 动态代理原理
FeignClientFactoryBean实现了FactoryBean<Object>接口
当RemoteSystemService被创建时,会调用FeignClientFactoryBean来创建。
FeignClientFactoryBean.getTarget创建出Feign.Builder
Feign.Builder.build创建出SynchronousMethodHandler.Factory(用于创建反射方法)
Feign.Builder.build创建出Client.Default(使用Java原生HttpURLConnection调用),Client是用来做远程调用的连接对象,如果pom加载OkHttp,也可以被OkHttp实现。
获取org.springframework.cloud.openfeign.DefaultTargeter(Target接口只能找到一个实现类)Targeter是用来做代理的中间对象。
通过resolveTarget方法获取HardCodedTarget<RemoteSystemService>
HardCodedTarget存取实际的调用信息
获取Target实现类DefaultTargeter通过Feign.Builder创建出ReflectiveFeign,
这段有点复杂,实际调用链是:FeignClientFactoryBean中的targeter.target --> DefaultTargeter中的feign.target --> Feign.target --> Feign.build()创建ReflectiveFeign --> ReflectiveFeign.newInstance创建出实际代理类。
ReflectiveFeign通过newInstance方法创建代理getTenantByTenantId(java.lang.Long),
target作用是存储远程调用信息,实际是HardCodedTarget对象。
最终返回了一个对getTenantByTenantId(java.lang.Long)的代理
FeignClient代理模式,使用FeignInvocationHandler持有一个对FeignClient注解接口的代理(Proxy),将注解接口中的MVC方法解析出来(例如GetMapping注解的方法),将接口包含的所有MVC方法存放到一个HashMap(dispatch)中,为后续提供调用。
对SpringMVC注解的解析
ReflectiveFeign.newInstance中,对Map<Method, MethodHandler> methodToHandler = targetToHandlersByName.apply(target, requestContext)的说明。通过使用OpenFeign.support.SpringMvcContract#parseAndValidateMetadata方法,可以从Controller方法的@GetMapping中解析出MethodMetadata,然后使用这个MethodMetadataton通过createMethodHandler方法来创建MethodHandler。
在ReflectiveFeign的ParseHandlersByName中的apply方法中做出解析。
MethodMetadata中的对@GetMapping注解内容的解析:
OpenFeign 发送请求的原理
ReflectiveFeign.invoke中,通过Method对象,获取到SynchronousMethodHandler对象
最终调用的是SynchronousMethodHandler的invoke方法。
然后在SynchronousMethodHandler中,executeAndDecode方法调用了Client的execute
本次的Client是由Feign.Builder默认创建出来的
通过convertAndSend调用Java原生的HttpURLConnection对象实现Http请求
java.net.HttpURLConnection是抽象类
sun.net.www.protocol.http.HttpURLConnection是实现类
另外,如果增加okhttp依赖的话,就会使用okhttp作为客户端,来代替Client.Default(创建原生HttpURLConnection)。
Okhttp会使用连接池的方式来管理连接
这里就不在使用Client.Default创建连接,而是使用OkHttpClient了
FeignClientFactoryBean做了哪些事?
通过getObject创建FeignClient客户端
获取FeignClientFactory工厂
创建Feign.Builder
根据Client接口的实现情况,创建Client接口对应的实现,默认选择Client.Default
根据Targeter接口的实现情况,创建Targeter接口对应的实现,默认选择DefaultTargeter
根据Target接口的实现情况,创建Target接口对应的实现,默认选择Target.HardCodedTarget
在有URL的情况下,创建Proxy代理
在没有配置URL的情况下,查找负载均衡的Feign代理
ReflectiveFeign做了哪些事?
对Feign接口进行代理
解析target(FeignClient注解的方法)对象,生成MethodMetadata
根据MethodMetadata生成MethodHandler集合
根据MethodHandler集合生成InvocationHandler
根据InvocationHandler生成Proxy对象
将MethodHandler集合绑定到Proxy
实现一个InvocationHandler接口默认的FeignInvocationHandler
在调用Feign接口时,承担invoke的入口,将MethodHandler调起
为OpenFeign增加简单负载均衡
初始化过程
FeignClientFactoryBean.getTarget
调用过程
FeignBlockingLoadBalancerClient.execute调用BlockingLoadBalancerClient.choose选择服务器
也可以结合Nacos创建功能更强大的负载均衡。