SpringCloud @FeignClient注入Spring容器原理分析
前言
本文分析@FeignClient注解如何别扫描并注入到spring容器中,重点分析 @EnableFeignClients工作原理。由于通过源码分析涉及内容比较多建议根据文章中流程debug调试进行学习。
文章涉及 容器刷新模板方法,ConfigurationClassPostProcessor(bean工厂后置处理器),@Import注解等工作原理分析
@EnableFeignClients分析
在分析前先提出几个问题:
- @EnableFeignClients通过什么原理可以把自己加到spring启动的生命周期中完成feign的bean扫描?
- Sprintboot run方法如何能扫描 bean definition并放入spring容器中的?
- Springboot启动阶段设置了哪些BeanFactoryPostProcessor到容器中?
本文在分析的过程中会将上述问题逐一讲解。在@EnableFeignClients注解中可以看到该注解主要功能:
- 扫描声@FeignClient 注解声明的类
- @FeignClient注解的类注入后可通过@Autowire @Component方式进行使用。类似@Configuration。
真正实现这些功能其实通过@Import注解+FeignClientsRegistrar类实现。
@Import 注解在spring启动生命周期中通过组合 ImportSelector实现类或者 ImportBeanDefinitionRegistrar实现类完成bean definition 加载
@EnableFeignClients就是用过这种机制完成@FeignClient的扫描
在springboot中@Import 注解加载bean definition是通过Spring的后置处理器 BeanFactoryPostProcessor完成。
源码调用分析
下面结合Springboot整体启动的流程分析下@EnableFeignClients如何被加载的,主要分析关键逻辑具体细节不在此处展开。
- 首先SpringApplication run 方法启动
- 执行refresh方法 该方法为 AbstractApplicationContext 模板方法
- 执行 invokeBeanFactoryPostProcessors方法 该方法会将实现了BeanDefinitionRegistryPostProcessor类的后置处理进行实例化并调用
- 执行 ConfigurationClassPostProcessor 后置处理处理@ComponentScan @Import @ImportResources @PropertySource等注解
- 调用FeignClientsRegistrar类的解析bean definition方法
接下来分析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<String> 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<AnnotationAttributes> 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<BeanDefinitionHolder> 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<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader"); for (String resource : resources) { String resolvedResource = this.environment.resolveRequiredPlaceholders(resource); configClass.addImportedResource(resolvedResource, readerClass); } } // Process individual @Bean methods Set<MethodMetadata> 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注解调用逻辑
解析Import注解中value并返回所有类
开始加载bean定义
loadBeanDefinitionsForConfigurationClass 方法开始加载Import注解中配置类。
通过调用栈信息最终找到执行FeignClientRegistrar接口
SpringBoot 注解加载流程逻辑
为了对Springboot中各个注解是在Spring生命周期每个阶段时如何执行的可以参考下图,具体流程可以单步debug进行分析
总结
本文简单分析了SpringBoot加载bean definition与FeignClient加载流程,由于细节逻辑太多本文不在展开分析。
相关文章
Java Spring @Autowired的这些骚操作,你都知道吗
这篇文章主要介绍了彻底搞明白Spring中的自动装配和Autowired注解的使用,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧2021-09-09
最新评论