EnableFeignClient源码探究
话不多说直接开干!
点进@EnableFeignClient
这个注解里:
里面较为核心的是这个FeignClientRegistrar,我们点进去:
我们发现它实现了ImportBeanDefinitionRegistrar、ResourceLoaderAware、EnvironmentAware这三个接口,这里关于后面连个Aware接口就不做过多介绍了,熟悉Spring的同学应该都知道吧,我们就看一下这个ImportBeanDefinitionRegistrar
这个是干嘛用的呢?我们二话不说,去ImportBeanDefinitionRegistrar上断点,学习一个框架最快的途径就是Debug,从头到尾Debug一番你就什么都了解了。
打上断点Debug启动后,可以看到它已经走到了这一行,第一个是registerDefaultConfiguration
顾名思义,是注册默认配置,我们跟进去看看有什么名堂:
可以看到,它这里通过getAnnotationAttributes
获取到了@EnableFeignClients
注解里面所有的属性,只不过没有赋值。
紧接着:
if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
String name;
//判断有没有父类
if (metadata.hasEnclosingClass()) {
name = "default." + metadata.getEnclosingClassName();
}
else {
//如果没有,就获取该类的全限定类名
name = "default." + metadata.getClassName();
}
registerClientConfiguration(registry, name,
defaultAttrs.get("defaultConfiguration"));
}
因为我们没有继承其他类,所以我们这里获取的name是:
最后又调用了registerClientConfiguration
这个方法,我们点进去看看这个方法做了什么事情:
我们可以看到这个方法里,它传进来了一个BeanDefinitionRegistry,通过这个registry就构造出来了上下文中用到的Bean信息。
这个Builder先构造了FeignClientSpecification,然后把main函数的类名传进去,并且把配置项也传进去,然后就从这个BeanDefinitionRegistry里把Bean注册进去了。
然后我们再返回到registerBeanDefinitions
这个方法里:
我们刚刚看完了第一个方法,是注册默认的配置,接下来这个registerFeignClients
方法就和Feign息息相关了。
这个FeignClients就是这些东西:
我们来看看它是怎么注册的:
首先第一步,它是获取了一个scanner,这个scanner就是扫包用的,下面有设置了一个resourceLoader,平淡无奇,继续往下看:
在这里又一次获取了EnableFeignClients,并且下面声明了一个Filter,这个Filter它会过滤这个FeignClient注解的一些类。
接下来:
这里判断欲奴attrs是否为null,如果是不是null,就获取到声明的clients,因为我们在注解里没有指定要加载哪些clients,所以这个attrs就是空,紧接着:
这里如果没有指定加载哪些clients,就走到了这一步,这里用到了刚刚的Filter,就是加载所有用FeignClient注解声明的类,下面又从matedata里面获取了一个basePackages,我们跟进去看看是怎么获取的:
protected Set<String> getBasePackages(AnnotationMetadata importingClassMetadata) {
//从EnableFeignClients里获取属性
Map<String, Object> attributes = importingClassMetadata
.getAnnotationAttributes(EnableFeignClients.class.getCanonicalName());
Set<String> basePackages = new HashSet<>();
//下面这几个for循环都是获取属性值
for (String pkg : (String[]) attributes.get("value")) {
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}
for (String pkg : (String[]) attributes.get("basePackages")) {
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}
for (Class<?> clazz : (Class[]) attributes.get("basePackageClasses")) {
basePackages.add(ClassUtils.getPackageName(clazz));
}
//如果都没有声明
if (basePackages.isEmpty()) {
basePackages.add(
//拿当前声明的main函数的那个类来扫描
ClassUtils.getPackageName(importingClassMetadata.getClassName()));
}
return basePackages;
}
由于我们这里都没有声明,所以它走了备选方案,获取了EnableFeignClient声明的那个main函数的包。
我们获取到了这个basePackage后,来到了这里:
这里它就会循环这个basePackages,然后利用scanner对basePackage进行扫包,之前我们看到这个scanner加了一个AnnotationTypeFilter,那它就会把这个包路径下的所有加载了对应Annotation的Class或者Interface给找出来,我们往下走一步看看:
他给我们找出来了这个IService,然后它循环这个candidate:
首先,因为我们的IService是用FeignClient这个注解声明的,所以它肯定是AnnotatedBeanDefinition这种类型,所以进入到了if里面:
if (candidateComponent instanceof AnnotatedBeanDefinition) {
// verify annotated class is an interface
//转换成AnnotatedBeanDefinition
AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
//获取Metadata,也就是获取主数据
AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
//这里要求annotationMetadata必须是一个接口,否则就报错
Assert.isTrue(annotationMetadata.isInterface(),
"@FeignClient can only be specified on an interface");
//然后从annotationMetadata拿到FeignClient属性
Map<String, Object> attributes = annotationMetadata
.getAnnotationAttributes(
FeignClient.class.getCanonicalName());
//获取clientName
String name = getClientName(attributes);
//注册配置信息
registerClientConfiguration(registry, name,
attributes.get("configuration"));
//注册FeignClient
registerFeignClient(registry, annotationMetadata, attributes);
}
我们来看这一步,它是怎么获取ClientName的:
跟进去:
首先,它看你注解里有没有声明contextId,如果没有,就会获取value:
这个value就是我们注解里声明的eureka-client。
如果value没拿到就从name里拿,name里没有就从serviceId拿,如果什么都拿不到,那么就会报错:
也就是说,当我们声明了FeignClient这个注解后,你必须给他指定一个serviceId,或者name、value等。
总之你要让它知道这个接口它要代理的对象,是需要把请求转发到哪一个微服务当中的,没有这个属性Feign可是没法工作的。
我们再看下一个方法:
跟进去:
这一步是注册Client的配置信息,实际上这一波就是在上下文中把BeanDefinitioin构造出来。
非常简单,就是传入name、配置项,最终再registry里面把这个Bean给注册进去。
我们返回:
最后这一步就是注册FeignClient了,跟进去看看:
前面简单,首先获取了Feign声明的类的全限定类名,然后声明了一个BeanDefinitionBuilder,然后这里对属性进行了一番验证,我们跟进去看一下:
这里是涉及Hystrix降级容错的一些逻辑,我们暂且先不管。
再返回:
这里进行完一大堆属性的设置后,这里设置了一个注入类型,是根据类型进行注入。
我们再往下走,看到这里:
这里构造了一个BeanDefinitionHolder,并且齐了一个别名,然后通过registerBeanDefinition
和BeanDefinitionRegistry关联了起来,完成了注册。
到这里我们整个registerBeanDefinitions
的过程已经结束了。