1写作目录
之前自己写过一个RPC框架demo,遇到两个问题没有解决。
在consumer端怎么找到被代理的接口呢?
比如用这个@FeignClient注解,正常情况下Spring是识别不到的,那是怎么识别到的呢?
接口如何代理呢?
之前的动态代理和静态代理都是先生成一个类,然后在去代理,但是在consumer端是没有接口实现类的,那怎么实现代理的呢?
因为解决这两个问题,也因为一些机缘巧合,看了部分Feign的源码,从而理解了这其中的逻辑,下面给大家分析并记录一下这个问题。
2前提
了解SpringBoot的自动装配原理,否则跟不上
3环境搭建
下载地址:https://github.com/cbeann/SpringCloudDemoHoxton
如下图所示,其中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实现的。