本文分析@FeignClient注解如何别扫描并注入到spring容器中,重点分析 @EnableFeignClients工作原理。由于通过源码分析涉及内容比较多建议根据文章中流程debug调试进行学习。
文章涉及 容器刷新模板方法,ConfigurationClassPostProcessor(bean工厂后置处理器),@Import注解等工作原理分析
在分析前先提出几个问题:
@EnableFeignClients通过什么原理可以把自己加到spring启动的生命周期中完成feign的bean扫描?
Sprintboot run方法如何能扫描 bean definition并放入spring容器中的?
Springboot启动阶段设置了哪些BeanFactoryPostProcessor到容器中?
本文在分析的过程中会将上述问题逐一讲解。在@EnableFeignClients注解中可以看到该注解主要功能:
真正实现这些功能其实通过@Import注解+FeignClientsRegistrar类实现。
@Import 注解在spring启动生命周期中通过组合 ImportSelector实现类或者 ImportBeanDefinitionRegistrar实现类完成bean definition 加载
@EnableFeignClients就是用过这种机制完成@FeignClient的扫描
在springboot中@Import 注解加载bean definition是通过Spring的后置处理器 BeanFactoryPostProcessor完成。
下面结合Springboot整体启动的流程分析下@EnableFeignClients如何被加载的,主要分析关键逻辑具体细节不在此处展开。
接下来分析AbstractApplicationContext 的refresh方法中invokeBeanFactoryPostProcessors调用逻辑。此方法主要实例化 BeanFactoryPostProcessor并调用 postProcessBeanFactory方法。特别提示所有BeanFactoryPostProcessor实例化一定要在所有bean初始化前。
重点分析invokeBeanFactoryPostProcessors方法及bean后置处理器调用逻辑
PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
方法逻辑比较长但很好理解下图中红色框逻辑完全一样都是从当前bean定义中找到 BeanDefinitionRegistryPostProcessor实现类然筛选出优先级注解类 PriorityOrdered跟排序注解类Ordered并调用完成所有bean的扫描并注册到容器中扫描来源分为:注解&xml。
完成所有bean定义扫描类的后置处理器为 ConfigurationClassPostProcessor
ConfigurationClassPostProcessor 的 postProcessBeanDefinitionRegistry方法开始解析bean 定义。
postProcessBeanDefinitionRegistry中核心逻辑是通过配置类解析器进行解析,配置类一般为Springboot中@SpringbootApplication注解修饰类。
此处为Springboot启动时解析入口 ,通过配置类分析
doProcessConfigurationClass方法开始解析各种常用注解如:@Component @Import等
protected final SourceClass doProcessConfigurationClass(
ConfigurationClass configClass, SourceClass sourceClass, Predicate filter)
throws IOException {
if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
// Recursively process any member (nested) classes first
processMemberClasses(configClass, sourceClass, filter);
}
// Process any @PropertySource annotations
for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), PropertySources.class,
org.springframework.context.annotation.PropertySource.class)) {
if (this.environment instanceof ConfigurableEnvironment) {
processPropertySource(propertySource);
}
else {
logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
"]. Reason: Environment must implement ConfigurableEnvironment");
}
}
// Process any @ComponentScan annotations
Set componentScans = AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
if (!componentScans.isEmpty() &&
!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
for (AnnotationAttributes componentScan : componentScans) {
// The config class is annotated with @ComponentScan -> perform the scan immediately
Set scannedBeanDefinitions =
this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
// Check the set of scanned definitions for any further config classes and parse recursively if needed
for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
if (bdCand == null) {
bdCand = holder.getBeanDefinition();
}
if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
parse(bdCand.getBeanClassName(), holder.getBeanName());
}
}
}
}
// Process any @Import annotations
processImports(configClass, sourceClass, getImports(sourceClass), filter, true);
// Process any @ImportResource annotations
AnnotationAttributes importResource =
AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
if (importResource != null) {
String[] resources = importResource.getStringArray("locations");
Class readerClass = importResource.getClass("reader");
for (String resource : resources) {
String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
configClass.addImportedResource(resolvedResource, readerClass);
}
}
// Process individual @Bean methods
Set beanMethods = retrieveBeanMethodMetadata(sourceClass);
for (MethodMetadata methodMetadata : beanMethods) {
configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
}
// Process default methods on interfaces
processInterfaces(configClass, sourceClass);
// Process superclass, if any
if (sourceClass.getMetadata().hasSuperClass()) {
String superclass = sourceClass.getMetadata().getSuperClassName();
if (superclass != null && !superclass.startsWith("java") &&
!this.knownSuperclasses.containsKey(superclass)) {
this.knownSuperclasses.put(superclass, configClass);
// Superclass found, return its annotation metadata and recurse
return sourceClass.getSuperClass();
}
}
// No superclass -> processing is complete
return null;
}
本文分析@Import注解调用逻辑
开始加载bean定义
loadBeanDefinitionsForConfigurationClass 方法开始加载Import注解中配置类。
通过调用栈信息最终找到执行FeignClientRegistrar接口
为了对Springboot中各个注解是在Spring生命周期每个阶段时如何执行的可以参考下图,具体流程可以单步debug进行分析
本文简单分析了SpringBoot加载bean definition与FeignClient加载流程,由于细节逻辑太多本文不在展开分析。