1写作目录
之前自己写过一个RPC框架demo,遇到两个问题没有解决。
- 在consumer端怎么找到被代理的接口呢?
比如用这个@FeignClient注解,正常情况下Spring是识别不到的,那是怎么识别到的呢? - 接口如何代理呢?
之前的动态代理和静态代理都是先生成一个类,然后在去代理,但是在consumer端是没有接口实现类的,那怎么实现代理的呢?
因为解决这两个问题,也因为一些机缘巧合,看了部分Feign的源码,从而理解了这其中的逻辑,下面给大家分析并记录一下这个问题。
2前提
了解SpringBoot的自动装配原理,否则跟不上
3环境搭建
下载地址:https://github.com/cbeann/SpringCloudDemoHoxton
参考视频:feign环境搭建demo
如下图所示,其中Service层就是Feign接口,Controller层调用Service的Feign接口
4 源码分析
4.1如何找到@FeignClient标注的接口
4.1.1添加注解引入目标类
在consumer端一般会加@EnableFeignClients
注解,其实这是一个复合注解,点进去看一下可以发现
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
引入了FeignClientsRegistrar.class
这个类并加载到IOC容器中。然后就会到达registerBeanDefinitions方法并在该方法里调用registerFeignClients方法
//FeignClientsRegistrar.java
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
registerDefaultConfiguration(metadata, registry);
registerFeignClients(metadata, registry);
}
4.1.2寻找代理接口
1)首先获取带有EnableFeignClients
注解的启动类的包路径
//FeignClientsRegistrar###registerFeignClients
Set<String> basePackages;
Map<String, Object> attrs = metadata
.getAnnotationAttributes(EnableFeignClients.class.getName());
basePackages = getBasePackages(metadata);
2)然后获取.class文件并解析。
下图为ClassPathScanningCandidateComponentProvider##scanCandidateComponents方法。
如果找到有@FeignClient
注解的则解析为ScannedGenericBeanDefinition
3)然后对上面的每一个ScannedGenericBeanDefinition
进行二次封装
此时到达FeignClientsRegistrar的registerFeignClient方法,把每一个ScannedGenericBeanDefinition
封装为AbstractBeanDefinition
,注意,此时的class类型为FeignClientFactoryBean.class
4)此时我们就明白了,Spring原来是通过解析.class文件获取@FeignClient
注解的interface并解析为类型为FeignClientFactoryBean.class
的BeanDefinition
,剩下的就是Bean的生命周期了。
4.2FeignClientFactoryBean的生命周期
我们先看一下FeignClientFactoryBean类的继承关系
class FeignClientFactoryBean
implements FactoryBean<Object>, InitializingBean, ApplicationContextAware
这个类继承了FactoryBean接口,那么在Bean的生命周期里肯定会调用FactoryBean的getObject方法,此时我们就是在FactoryBean的getObject方法上打断点,从这里开始debug
因为这是Bean的生命周期(了解Bean的生命周期并从调用栈可以看出),所以我们一直从上面的那个地方跟命令是return的方法,一直跟到ReflectiveFeign
的newInstance
方法,如下图所示,有没有发现,这个地方是不是似曾相识(不相识的话说明动态代理不熟)?
5总结
-
Feign是通过扫描.class文件定位到标有
@FeignClient
注解的类的,然后解析为类型为FeignClientFactoryBean.class
的BeanDefinition
,然后执行Bean的生命周期,最后调用FeignClientFactoryBean的getObject方法进行动态代理。
拓展:其实也可以通过BeanPostProcessor去实现上面的功能,当然不让上面的完美 -
其实远程调用有一个统一面临的问题,就是你是不知道远程调用类是什么类型的,那么怎么对这个类执行Bean的生命周期呢?或者换换句话说,BeanDefinition里面的类是哪个类呢?如果要做到统一,则可以使用FactoryBean接口,让类的创建发生延迟,其实Dobbo的源码中也是通过FactoryBean实现的。
6参考
诸葛老师VIP微服务调用组件Ribbon和Feign源码剖析
尚硅谷SpringCloud框架开发教程(SpringCloudAlibaba微服务分布式架构丨Spring Cloud)
花了一星期,自己写了个简单的RPC框架