本文源码基于 spring-cloud-starter-openfeign-3.1.8
@EnableFeignClients
点开@EnableFeignClients,可以看到@EnableFeignClients导入了一个FeignClientsRegistrar类:@Import(FeignClientsRegistrar.class)
,这个类继承了ImportBeanDefinitionRegistrar接口。关于@Import的原理可以看这篇文章:
https://blog.csdn.net/qq_40926260/article/details/142646615?spm=1001.2014.3001.5502
class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// 注册 FeignClientSpecification.class
registerDefaultConfiguration(metadata, registry);
//
registerFeignClients(metadata, registry);
}
}
registerDefaultConfiguration
registerDefaultConfiguration方法的作用在于向容器中注册FeignClientSpecification
这个BeanDefinition
// metadata:@EnableFeignClients 加在哪里,metadata 就是哪个类的注解元数据
// registry: Bean 注册中心,即 Spring 容器
private void registerDefaultConfiguration(AnnotationMetadata metadata, BeanDefinitionRegistry c) {
Map<String, Object> defaultAttrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName(), true);
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"));
}
}
private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name, Object configuration) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(FeignClientSpecification.class);
builder.addConstructorArgValue(name);
builder.addConstructorArgValue(configuration);
registry.registerBeanDefinition(name + "." + FeignClientSpecification.class.getSimpleName(),
builder.getBeanDefinition());
}
defaultConfiguration这个配置的作用在于FeignClientSpecification这个类有一个带参构造,会使用到这个参数
registerFeignClients
registerFeignClients方法意为注册所有的FeignClient
public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet<>();
Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());
final Class<?>[] clients = attrs == null ? null : (Class<?>[]) attrs.get("clients");
if (clients == null || clients.length == 0) {
// 扫描类路径下的 @FeignClient
ClassPathScanningCandidateComponentProvider scanner = getScanner();
scanner.setResourceLoader(this.resourceLoader);
scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
// 获取所有要扫描的包名
Set<String> basePackages = getBasePackages(metadata);
for (String basePackage : basePackages) {
candidateComponents.addAll(scanner.findCandidateComponents(basePackage));
}
} else {
// 禁用类路径下的 @FeignClient 扫描
for (Class<?> clazz : clients) {
candidateComponents.add(new AnnotatedGenericBeanDefinition(clazz));
}
}
// 得到所有的@FeignClient注解类型的Bean定义
for (BeanDefinition candidateComponent : candidateComponents) {
if (candidateComponent instanceof AnnotatedBeanDefinition) {
// verify annotated class is an interface
AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
Assert.isTrue(annotationMetadata.isInterface(), "@FeignClient can only be specified on an interface");
// 只有接口才进行注册
Map<String, Object> attributes = annotationMetadata
.getAnnotationAttributes(FeignClient.class.getCanonicalName());
String name = getClientName(attributes);
registerClientConfiguration(registry, name, attributes.get("configuration"));
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
类路径扫描
@EnableFeignClients注解的clients属性如果不为空,那么会进行类路径扫描,使用配置的FeignClient类型
类路径扫描用到了@EnableFeignClients的 3 个属性
- value:包名配置
- basePackages:包名配置
- basePackageClasses:使用指定Class的包名
如果 @EnableFeignClients 上这3种方式都没配置,才使用@EnableFeignClients加在的那个类的包名作为包扫描路径
protected Set<String> getBasePackages(AnnotationMetadata importingClassMetadata) {
Map<String, Object> attributes = importingClassMetadata
.getAnnotationAttributes(EnableFeignClients.class.getCanonicalName());
Set<String> basePackages = new HashSet<>();
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));
}
// 如果 @EnableFeignClients 上一个包名都没配置,才使用@EnableFeignClients加在的那个类的包名
if (basePackages.isEmpty()) {
basePackages.add(ClassUtils.getPackageName(importingClassMetadata.getClassName()));
}
return basePackages;
}
注册 @FeignClient
客户端名称的优先级,contextId -> name -> serviceId,如果都没有则报错
private String getClientName(Map<String, Object> client) {
if (client == null) {
return null;
}
String value = (String) client.get("contextId");
if (!StringUtils.hasText(value)) {
value = (String) client.get("value");
}
if (!StringUtils.hasText(value)) {
value = (String) client.get("name");
}
if (!StringUtils.hasText(value)) {
value = (String) client.get("serviceId");
}
if (StringUtils.hasText(value)) {
return value;
}
throw new IllegalStateException(
"Either 'name' or 'value' must be provided in @" + FeignClient.class.getSimpleName());
}
这个客户端名称是FeignClientSpecification所需的另外一个参数
注册@EnableFeignClients
和@FeignClient
都是注册的FeignClientSpecification这个类型,只是 Bean 的名称不同,形式都是:${name}.FeignClientSpecification
registerFeignClient
private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata,
Map<String, Object> attributes) {
String className = annotationMetadata.getClassName();
Class clazz = ClassUtils.resolveClassName(className, null);
ConfigurableBeanFactory beanFactory = registry instanceof ConfigurableBeanFactory
? (ConfigurableBeanFactory) registry : null;
// 获取 contextId ,支持表达式计算
String contextId = getContextId(beanFactory, attributes);
// serviceId -> name -> value,支持表达式计算
String name = getName(attributes);
FeignClientFactoryBean factoryBean = new FeignClientFactoryBean();
factoryBean.setBeanFactory(beanFactory);
factoryBean.setName(name);
factoryBean.setContextId(contextId);
factoryBean.setType(clazz);
factoryBean.setRefreshableClient(isClientRefreshEnabled());
BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(clazz, () -> {
factoryBean.setUrl(getUrl(beanFactory, attributes));
factoryBean.setPath(getPath(beanFactory, attributes));
factoryBean