前言
在Cloud 中使用@EnableFeignClients
启用Feign客户端,接下来分析下这个注解的基本原理。
作用
@EnableFeignClients
注解会扫描包路径下的@FeignClient
注解定义的接口,并注册到IOC容器中。
配置属性
配置属性主要是配置扫描路径:
@Retention(RetentionPolicy.RUNTIME)
@Target({
ElementType.TYPE})
@Documented
@Import({
FeignClientsRegistrar.class})
public @interface EnableFeignClients {
// 配置扫描@FeignClient的路径,默认是当前标识了该注解类下的路径,和@ComponentScans一样
String[] value() default {
};
// 扫描路径,多个
String[] basePackages() default {
};
// 指定扫描某个类所在包下的所有类
Class<?>[] basePackageClasses() default {
};
// 指定客户端配置配
Class<?>[] defaultConfiguration() default {
};
// 直接指定扫描客户端的类,配置了就不会扫描
Class<?>[] clients() default {
};
}
FeignClientsRegistrar
在@EnableFeignClients
注解中,使用了@Import
注解导入了FeignClientsRegistrar
,学过Spring 的应该知道@Configuration
类上使用@Import
,可以注入Bean 对象。
FeignClientsRegistrar
实现了以下几个接口:
- ImportBeanDefinitionRegistrar:支持使用@Import注解注册 BeanDefinition
- ResourceLoaderAware:读取文件
- EnvironmentAware:获取环境及配置属性
启动项目后,进行BeanDefinition
注册时,就会进入到registerBeanDefinitions
方法,该方法会注册默认配置和注册Feign 客户端。
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
this.registerDefaultConfiguration(metadata, registry);
this.registerFeignClients(metadata, registry);
}
registerDefaultConfiguration
会进行注册客户端配置类的BeanDefinition
:
// 参数为EnableFeignClients注解配置的类的信息、BeanDefinition注册器
private void registerDefaultConfiguration(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// 1. 获取EnableFeignClients注解上的配置属性
Map<String, Object> defaultAttrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName(), true);
if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
// 取个名字=》default.account.AccountApp
String name;
if (metadata.hasEnclosingClass()) {
name = "default." + metadata.getEnclosingClassName();
} else {
name = "default." + metadata.getClassName();
}
// 2. 注册客户端配置
this.registerClientConfiguration(registry, name, defaultAttrs.get("defaultConfiguration"));
}
}
接着进入到registerClientConfiguration
,注册客户端配置,会在注册器中添加一个名字为default.account.AccountApp.FeignClientSpecification
,类型为FeignClientSpecification
的BeanDefinition
。
private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name, Object configuration) {
// 创建一个 BeanDefinition构建者,使用FeignClientSpecification(这里存放了客户端配置名称和配置类)
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(FeignClientSpecification.class);
// 添加FeignClientSpecification 构造函数的参数
// name => default.account.AccountApp
// configuration=> 注解上的配置类,这里没有
builder.addConstructorArgValue(name);
builder.addConstructorArgValue(configuration);
// 注册,名称为=》default.account.AccountApp.FeignClientSpecification
// 类型为=》FeignClientSpecification
registry.registerBeanDefinition(name + "." + FeignClientSpecification.class.getSimpleName(), builder.getBeanDefinition());
}
接着注册Feign 客户端,会调用扫描器,扫描类路径下的@FeignClient
标记的类,然后读取配置信息,将该类的配置项、类信息都注册到注册器中。
public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// BeanDefinition 不重复集合
LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet();
// EnableFeignClients 注解配置信息
Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());
new AnnotationTypeFilter(FeignClient.class);
Class<?>[] clients = attrs == null ? null : (Class[])((Class[])attrs.get("clients"));
// 查看是否配置了clients 属性,看来这个属性配置了以后,就不会进行包扫描
if (clients != null && clients.length != 0) {
Class[] var13 = clients;
int var15 = clients.length;
for(int var17 = 0; var17 < var15; ++var17) {
Class<?> clazz = var13[var17];
candidateComponents.add(new AnnotatedGenericBeanDefinition(clazz));
}
} else {
// 创建一个ClassPath扫描器
ClassPathScanningCandidateComponentProvider scanner = this.getScanner();
// 设置ResourceLoader
scanner.setResourceLoader(this.resourceLoader);
// 设置需要扫描的类,也就是配置了FeignClient 注解的类
scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
// 获取配置上的 这里没配置,就是扫描当前项目包下面的了
Set<String> basePackages = this.getBasePackages(metadata);
Iterator var9 = basePackages.iterator();
// 扫描到Feign 接口类,放入集合中
while(var9.hasNext()) {
String basePackage = (String)var9.next();
candidateComponents.addAll(scanner.findCandidateComponents(basePackage));
}
}
Iterator var14 = candidateComponents.iterator();
while(var14.hasNext()) {
// 循环扫描到的BeanDefinition
BeanDefinition candidateComponent = (BeanDefinition)var14.next();
if (candidateComponent instanceof AnnotatedBeanDefinition) {
AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition)candidateComponent;
// FeignClient注解上的元信息
AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
Assert.isTrue(annotationMetadata.isInterface(), "@FeignClient can only be specified on an interface");
// FeignClient注解配置的属性
Map<String, Object> attributes = annotationMetadata.getAnnotationAttributes(FeignClient.class.getCanonicalName());
// BeanDefinition 名称=》order-service
String name = this.getClientName(attributes);
// 注册客户端配置=》configuration属性
this.registerClientConfiguration(registry, name, attributes.get("configuration"));
// 注册客户端
this.registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
注册了客户端的配置后,就会注册客户端,调用的是registerFeignClient
方法:
private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
// account.OrderFeign
String className = annotationMetadata.getClassName();
// 创建FeignClientFactoryBean 类型的BeanDefinition
BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);
this.validate(attributes);
// 将这些FeignClient 注解上的配置信息都添加到definition中,
definition.addPropertyValue("url", this.getUrl(attributes));
// 省略....
beanDefinition.setPrimary(primary);
String qualifier = this.getQualifier(attributes);
if (StringUtils.hasText(qualifier)) {
alias = qualifier;
}
// 添加到注册器中
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, new String[]{
alias});
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}
可以看到,最终每个Feign 接口注册到了 注册器中,名称为包名+接口名
,Feign 接口实际生成的Bean对象为FeignClientFactoryBean
,这是一个FactoryBean
,由它最终生成的代理对象后续会讲解)。