SpringCloud @FeignClient注入Spring容器原理分析
前言
本文分析@FeignClient注解如何別掃描并注入到spring容器中,重點(diǎn)分析 @EnableFeignClients工作原理。由于通過源碼分析涉及內(nèi)容比較多建議根據(jù)文章中流程debug調(diào)試進(jìn)行學(xué)習(xí)。
文章涉及 容器刷新模板方法,ConfigurationClassPostProcessor(bean工廠后置處理器),@Import注解等工作原理分析
@EnableFeignClients分析
在分析前先提出幾個(gè)問題:
- @EnableFeignClients通過什么原理可以把自己加到spring啟動(dòng)的生命周期中完成feign的bean掃描?
- Sprintboot run方法如何能掃描 bean definition并放入spring容器中的?
- Springboot啟動(dòng)階段設(shè)置了哪些BeanFactoryPostProcessor到容器中?
本文在分析的過程中會(huì)將上述問題逐一講解。在@EnableFeignClients注解中可以看到該注解主要功能:
- 掃描聲@FeignClient 注解聲明的類
- @FeignClient注解的類注入后可通過@Autowire @Component方式進(jìn)行使用。類似@Configuration。
真正實(shí)現(xiàn)這些功能其實(shí)通過@Import注解+FeignClientsRegistrar類實(shí)現(xiàn)。
@Import 注解在spring啟動(dòng)生命周期中通過組合 ImportSelector實(shí)現(xiàn)類或者 ImportBeanDefinitionRegistrar實(shí)現(xiàn)類完成bean definition 加載
@EnableFeignClients就是用過這種機(jī)制完成@FeignClient的掃描
在springboot中@Import 注解加載bean definition是通過Spring的后置處理器 BeanFactoryPostProcessor完成。
源碼調(diào)用分析
下面結(jié)合Springboot整體啟動(dòng)的流程分析下@EnableFeignClients如何被加載的,主要分析關(guān)鍵邏輯具體細(xì)節(jié)不在此處展開。
- 首先SpringApplication run 方法啟動(dòng)
- 執(zhí)行refresh方法 該方法為 AbstractApplicationContext 模板方法
- 執(zhí)行 invokeBeanFactoryPostProcessors方法 該方法會(huì)將實(shí)現(xiàn)了BeanDefinitionRegistryPostProcessor類的后置處理進(jìn)行實(shí)例化并調(diào)用
- 執(zhí)行 ConfigurationClassPostProcessor 后置處理處理@ComponentScan @Import @ImportResources @PropertySource等注解
- 調(diào)用FeignClientsRegistrar類的解析bean definition方法
接下來分析AbstractApplicationContext 的refresh方法中invokeBeanFactoryPostProcessors調(diào)用邏輯。
此方法主要實(shí)例化 BeanFactoryPostProcessor并調(diào)用 postProcessBeanFactory方法。
特別提示所有BeanFactoryPostProcessor實(shí)例化一定要在所有bean初始化前。
重點(diǎn)分析invokeBeanFactoryPostProcessors方法及bean后置處理器調(diào)用邏輯
PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
方法邏輯比較長但很好理解下圖中紅色框邏輯完全一樣都是從當(dāng)前bean定義中找到 BeanDefinitionRegistryPostProcessor實(shí)現(xiàn)類然篩選出優(yōu)先級(jí)注解類 PriorityOrdered跟排序注解類Ordered并調(diào)用完成所有bean的掃描并注冊到容器中掃描來源分為:注解&xml。
完成所有bean定義掃描類的后置處理器為 ConfigurationClassPostProcessor
ConfigurationClassPostProcessor 的 postProcessBeanDefinitionRegistry方法開始解析bean 定義。
postProcessBeanDefinitionRegistry中核心邏輯是通過配置類解析器進(jìn)行解析,配置類一般為Springboot中@SpringbootApplication注解修飾類。
此處為Springboot啟動(dòng)時(shí)解析入口 ,通過配置類分析
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注解調(diào)用邏輯
解析Import注解中value并返回所有類
開始加載bean定義
loadBeanDefinitionsForConfigurationClass 方法開始加載Import注解中配置類。
通過調(diào)用棧信息最終找到執(zhí)行FeignClientRegistrar接口
SpringBoot 注解加載流程邏輯
為了對Springboot中各個(gè)注解是在Spring生命周期每個(gè)階段時(shí)如何執(zhí)行的可以參考下圖,具體流程可以單步debug進(jìn)行分析
總結(jié)
本文簡單分析了SpringBoot加載bean definition與FeignClient加載流程,由于細(xì)節(jié)邏輯太多本文不在展開分析。
相關(guān)文章
Spring?Data?JPA實(shí)現(xiàn)查詢結(jié)果返回map或自定義的實(shí)體類
這篇文章主要介紹了Spring?Data?JPA實(shí)現(xiàn)查詢結(jié)果返回map或自定義的實(shí)體類,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12JavaEE簡介_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
這篇文章主要介紹了JavaEE簡介,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-07-07java運(yùn)行錯(cuò)誤A JNI error的解決方案
這篇文章主要介紹了java運(yùn)行錯(cuò)誤A JNI error的解決方案,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-08-08解決OpenFeign遠(yuǎn)程調(diào)用返回的對象總是null問題
OpenFeign在SpringCloud中用于遠(yuǎn)程調(diào)用,配置簡單,在使用Ribbon或Hystrix時(shí),需要注意path參數(shù)必須以/開頭,否則回參會(huì)是null2024-11-11SpringBoot中Elasticsearch的連接配置原理與使用詳解
Elasticsearch是一種開源的分布式搜索和數(shù)據(jù)分析引擎,它可用于全文搜索、結(jié)構(gòu)化搜索、分析等應(yīng)用場景,本文主要介紹了SpringBoot中Elasticsearch的連接配置原理與使用詳解,感興趣的可以了解一下2023-09-09El表達(dá)式使用問題javax.el.ELException:Failed to parse the expression
今天小編就為大家分享一篇關(guān)于Jsp El表達(dá)式使用問題javax.el.ELException:Failed to parse the expression的解決方式,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧2018-12-12