为什么要用fengin?
Feign 就是来简化我们发起远程调用的代码的,那简化到什么程度呢?简化成就像调用本地方法那样简单。
opengin如何使用?
先定义一个接口:
第二步:
@EnableFeignClients(basePackages = "com..*)
第三步:服务定义一个方法,其方法路径服务中的接口 URL 地址一致即可。
第四步 服务的 POM 文件中引入 OpenFeign 组件。
第五步:直接调用
@Autowired
private RemoteUserService remoteUserService;
R<LoginUser> userResult = remoteUserService.getUserInfo(username, SecurityConstants.INNER)
加了 @FeignClient 注解的接口后,我们就可以调用它定义的接口,然后就可以调用到远程服务了。
核心流程:
OpeFeign 包扫描原理:
我们需要在程序启动处添加@EnableFeignClients注解,该注解会导入FeignClientsRegistrar类进入到spring容器中,该类实现了ImportBeanDefinitionRegistrar接口,在spring启动的过程中会调用FeignClientsRegistrar中实现的registerBeanDefinitions,,那我们就去看下FeignClientsRegistrar#registerBeanDefinitions方法。
上源码:
第一部分是为了找到@Feignclient标识的接口类,第二部分就是对找出的接口类进行处理了处理,主要关注registerClientConfiguration和registerFeignClient函数。
其中registerClientConfiguration是为了处理@FeignClient#configuration属性的,在这个函数会往spirng容器中添加#{serviceName}.FeignClientSpecification作为名字的FeignClientSpecification类对象
registerFeignClient函数:
上面截图的就是OpenFeign对于接口的动态实现。这时候可能会有同学问,什么,真的么,factoryBean是啥东东,这代码里我没有看到半点动态代理的影子。
确实在上面我们只看到factoryBean的组装过程,我们看到往factoryBean中塞了好多属性,只有最后调用了factoryBean.getObject()产生了BeanDefinitionBuilder对象,这个对象说的是OpenFeign对于接口的动态实现类。那么我们就一起来看下factoryBean是个神马东西,
经过分析是jdk的动态代理实现:
总结:
调用具体实现:
feign.SynchronousMethodHandler#invoke
feign.SynchronousMethodHandler#executeAndDecode
代码上说明的就是如果配置了decode对象,那么直接用decode去解码reponse,如果没有配置,则使用AsyncResponseHandler#handleResponse方法去处理reponse,那么首先这个deocde是在哪里配置的呢?经过我代码搜寻,发现了这个是创建SynchronousMethodHandler对象时传入的forceDecoding参数来控制的,具体截图就不放了,大家可以自己去找下,并且这forceDecoding默认值是false,也就是说,一般情况下处理reponse的方式都是走的AsyncResponseHandler#handleResponse方法,这个方法里就有OpenFeign对于response的各种处理。
最后大家可能会有疑问,哇,这个处理reponse的AsyncResponseHandler,看这个名字是异步的呀,那OpenFeign难道也是异步返回结果的么?答案是否定的,具体我们来看下代码
这边也是第三部分的代码截图,只是上面最后的一丢丢截图截不下了,可以看到,这边最后是调用了CompleteFuture#join方法,这个方法会堵塞主线程,直到resultFuture结果返回,也就是说处理reponse的过程是异步的,但是返回结果还是同步的,在最后结果处理结束之前,主线程都是堵塞的。
负载均衡实现
构造
FeignBlockingLoadBalancerClient传入了负载均衡客户端LoadBalancerClient及负载均衡客户端工厂LoadBalancerClientFactory该工厂是用来创建每一个Feign客户端对应的子容器
AnnotationConfigApplicationContext及从对应子容器获取相应的Bean实例对象,如:Client,Request.Options,Logger.Level等。
扩展链路追踪实现:
这段代码就是创建了一个RequestInterceptor对象,他的作用就是我们在使用OpenFeign调用时往http请求的head上添加traceId,方便我们进行链式追踪。
总结
本文通过我的开源项目 PassJava 中用到的 OpenFeign 作为示例代码作为入口进行讲解。然后以图解+解读源码的方式深入剖析了 OpenFeign 的运行机制和架构设计。
核心思想:
OpenFeign 会扫描带有 @FeignClient 注解的接口,然后为其生成一个动态代理。
动态代理里面包含有接口方法的 MethodHandler,MethodHandler 里面又包含经过 MVC Contract 解析注解后的元数据。
发起请求时,MethodHandler 会生成一个 Request。
负载均衡器 Ribbon 会从服务列表中选取一个 Server,拿到对应的 IP 地址后,拼接成最后的 URL,就可以发起远程服务调用了。
面试题:
补充说明: Feign的底层用的是Ribbon,但超时时间以Feign配置为准