OpenFeign启动流程(一):初始化加载@EnableFeignClients
本章开始,我们要深入学习和了解SpringCloud生态中,一个很重要的组件 — OpenFeign
在正式讲解之前,我希望您已经初步了解了openFeign的正确使用方法,本章我们将会从Feign的入口注解开始,逐步进行源码的剖析
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-openfeign</artifactId>
<version>4.1.0-SNAPSHOT</version>
<relativePath>..</relativePath>
</parent>
1.初始化入口@EnableFeignClients
组件的加载通常需要进行初始化操作,而找到代码的入口点则变得至关重要。如果我们能够准确定位起始入口,就能够顺藤摸瓜地深入源码分析。
对于包含 OpenFeign 的主程序入口,一般呈现如下形式:
@SpringBootApplication
@EnableFeignClients ①
public class FeignApplication {
public static void main(String[] args) {
SpringApplication.run(FeignApplication.class, args);
}
}
① @EnableFeignClients
是 OpenFeign 为 Spring Boot 项目提供的启动注解,只有包含该注解才能够使用 Feign 客户端功能。
/**
* 启用 Feign 客户端的注解,允许在 Spring Boot 项目中使用 Feign 客户端功能。
* 通过导入 {@link FeignClientsRegistrar} 注册类,实现 Feign 客户端的初始化。
* @see FeignClientsRegistrar
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({FeignClientsRegistrar.class})
public @interface EnableFeignClients {
String[] value() default {};
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
Class<?>[] defaultConfiguration() default {};
Class<?>[] clients() default {};
}
1.1 FeignClientsRegistrar 注册导入类
这里引入了 FeignClientsRegistrar
注册类,我们需要深入探讨后续内容
class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAwar
这个类实现了 ImportBeanDefinitionRegistrar
接口,表示它负责注册 BeanDefinition。我们将重点关注该类中最为关键的逻辑,即实现的 registerBeanDefinitions
方法。在这个方法中,包含两个主要步骤:
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
//生成默认的配置,也就是FeignClientSpecification
// 这个类和下一章的FeignClientFactoryBean有关联
this.registerDefaultConfiguration(metadata, registry); ①
//注册所有加入FeignClient注解的类
this.registerFeignClients(metadata, registry); ②
}
- ① 用于生成默认的配置信息,为使用者提供一些默认行为。这有助于简化 Feign 客户端的配置过程
- ② 用于注册所有添加了
FeignClient
注解的类。在这里,可能涉及扫描指定路径下的 Feign 客户端类,并将它们注册为 Spring 的 Bean。
1.2 registerDefaultConfiguration(metadata, registry)
private void registerDefaultConfiguration(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
//获取到EnableFeignClients注解类的元数据(也就是各个配置)
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();
}
//最后生成的name 是default.启动器类名的全路径
//注册FeignClientSpecification
this.registerClientConfiguration(registry, name, defaultAttrs.get("defaultConfiguration"));
}
}
- 获取到EnableFeignClients注解上的元数据(metaData)
- 判断是否存在@EnableFeignClients的类
- 注册FeignClientSpecification类
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());
}
registerClientConfiguration
是注册了一个FeignClientSpecification
,每一个FeignClient 都会有一个FeignClientSpecification
,它是用于做环境上下文隔离的重要功能
1.3 registerFeignClients(metadata, registry)
public void registerFeignClients(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
//用于扫描收集与指定条件匹配的组件类
ClassPathScanningCandidateComponentProvider scanner = getScanner();
//设置ResourceLoader 用于下一步扫描
scanner.setResourceLoader(this.resourceLoader);
Set<String> basePackages;
//获取元数据
Map<String, Object> attrs = metadata
.getAnnotationAttributes(EnableFeignClients.class.getName());
AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
FeignClient.class);
final Class<?>[] clients = attrs == null ? null
: (Class<?>[]) attrs.get("clients");
//在没有设置clients属性的时候会进入这里,也就是默认的情况下
if (clients == null || clients.length == 0) {
//配置只扫描该Filter类型的类 也就是加了@FeignClient的注解的类
scanner.addIncludeFilter(annotationTypeFilter);
//存在basePackages扫描 basePackages 下的
//如果value、basePackages、以及basePackageClasses 都没有的情况下
//扫描 启动器同级包下的
basePackages = getBasePackages(metadata);
}
else {
final Set<String> clientClasses = new HashSet<>();
basePackages = new HashSet<>();
for (Class<?> clazz : clients) {
basePackages.add(ClassUtils.getPackageName(clazz));
clientClasses.add(clazz.getCanonicalName());
}
AbstractClassTestingTypeFilter filter = new AbstractClassTestingTypeFilter() {
@Override
protected boolean match(ClassMetadata metadata) {
String cleaned = metadata.getClassName().replaceAll("\\$", ".");
return clientClasses.contains(cleaned);
}
};
scanner.addIncludeFilter(
new AllTypeFilter(Arrays.asList(filter, annotationTypeFilter)));
}
for (String basePackage : basePackages) {
//找到指定包下的所有包含@FeignClient的类
Set<BeanDefinition> candidateComponents = scanner
.findCandidateComponents(basePackage);
for (BeanDefinition candidateComponent : candidateComponents) {
if (candidateComponent instanceof AnnotatedBeanDefinition) {
// verify annotated class is an interface
AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
//拿到这个bean描述的注解元数据
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);
//注册到spring容器中
registerClientConfiguration(registry, name,
attributes.get("configuration"));
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
}
在 registerFeignClients
内部,执行了以下几个关键步骤:
- 配置扫描启动器
ClassPathScanningCandidateComponentProvider
scanner,指定扫描类为 FeignClient - 配置包扫描路径,通过 metadata 获取范围,生成 Set<String> basePackages。其内部通过
ClassUtils.getPackageName
扫描主启动类所在的包路径 - 遍历包路径,执行以下三个关键操作:
- getClientName:获取客户端名称
- registerClientConfiguration:注册客户端配置信息,在
- registerFeignClient:注册 Feign 客户端
private void registerFeignClient(BeanDefinitionRegistry registry,
AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
String className = annotationMetadata.getClassName();
//注意这个FeignClientFactoryBean 会单独拿出来说! 生成一个FeignClientFactoryBean类型的Builder构造器
//然后
BeanDefinitionBuilder definition = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientFactoryBean.class);
validate(attributes);
//添加配置项
definition.addPropertyValue("url", getUrl(attributes));
definition.addPropertyValue("path", getPath(attributes));
String name = getName(attributes);
definition.addPropertyValue("name", name);
String contextId = getContextId(attributes);
definition.addPropertyValue("contextId", contextId);
//这里的type就关联到挂载哪个类型下面了,这里的类型就是一个个使用@FeignClient注解的类
//也就是跟他们关联了以后才能通过FeignClientFactoryBean 也就是这个区分类型
// @Override
// public Class<?> getObjectType() {
// return this.type;
// }
definition.addPropertyValue("type", className);
definition.addPropertyValue("decode404", attributes.get("decode404"));
definition.addPropertyValue("fallback", attributes.get("fallback"));
definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));
//设置自动装配类型
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
String alias = contextId + "FeignClient";
AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
boolean primary = (Boolean) attributes.get("primary"); // has a default, won't be
// null
beanDefinition.setPrimary(primary);
//别名
String qualifier = getQualifier(attributes);
if (StringUtils.hasText(qualifier)) {
alias = qualifier;
}
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
new String[] { alias });
//注册
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}
在 registerFeignClient
方法中,会扫描项目中所有的 Feign 客户端,并生成代理类,接着注入 FeignClientFactoryBean
。下一步是深入了解 FeignClientFactoryBean
的内部逻辑:
class FeignClientFactoryBean implements FactoryBean<Object>, InitializingBean, ApplicationContextAware
FeignClientFactoryBean
实现了 FactoryBean<Object>
、InitializingBean
、ApplicationContextAware
接口,这说明它是一个特殊的工厂Bean,在 Spring 容器中生成 Feign 客户端代理对象的工具类。
在 FeignClientFactoryBean
的构造过程中,它会使用 BeanDefinitionBuilder
构建一个 FeignClientFactoryBean
的 BeanDefinition,并添加各种配置项。这些配置项包括了 Feign 客户端的各种属性,例如 URL、超时时间、拦截器等。
最终,FeignClientFactoryBean
将经过配置的 BeanDefinition 封装成 BeanDefinitionHolder
,并通过 Spring 容器的注册表进行注册。这个过程使得 Spring 容器能够管理 Feign 客户端的生命周期和配置。
//注册到spring容器,通过registry进行注入
//holder是BeanDefinition的包装类,可以简单理解为,在此基础上增加了别名等一些信息项的BeanDefinition
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);