springboot如何自动扫描添加BeanDefinition源码
springboot如何自动扫描添加BeanDefinition源码
这篇文章主要介绍了springboot如何自动扫描添加BeanDefinition源码,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。
1.
springboot启动过程中,首先会收集需要加载的bean的定义,作为BeanDefinition对象,添加到BeanFactory中去。
由于BeanFactory中只有getBean之类获取bean对象的方法,所以将将BeanDefinition添加到BeanFactory中,是通过BeanDefinitionRegistry接口的void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws BeanDefinitionStoreException;方法来完成的。
所以我们的BeanFactory的实现类如果需要具备通过beanName来返回bean对象和添加删除BeanDefinition的能力,至少实现BeanFactory和BeanDefinitionRegistry的这两个接口。
这里我们就来看看springboot是如何查找bean的定义,添加到BeanFactory中的。
由于我们这里只是关注查找bean对象的定义,所以这里我们这里提到的BeanFactory主要会关注BeanDefinitionRegistry这个接口。
我们本地主要分析springboot扫描加载bean的配置,和我们的代码关系不大,所以我们的代码就用最简单的吧。具体代码如下:
packagecom.example.bootargs;importorg.springframework.boot.SpringApplication;importorg.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplicationpublicclassBootargsApplication{publicstaticvoidmain(String[]args){SpringApplication.run(BootargsApplication.class,args);}}
后面提到的主类统一是com.example.bootargs.BootargsApplication
2.
Springboot 查找bean的定义主要是通过ConfigurationClassPostProcessor这个类来完成的。
ConfigurationClassPostProcessor实现了BeanDefinitionRegistryPostProcessor接口。BeanDefinitionRegistryPostProcessor接口就是通过postProcessBeanDefinitionRegistry方法来给BeanDefinitionRegistry的实现类来添加bean的定义。
BeanDefinitionRegistryPostProcessor继承了BeanFactoryPostProcessor接口,而BeanFactoryPostProcessor接口主要是用来对BeanFactory进行增强。在springboot启动过程中首先会创建BeanFactory,再调用BeanFactoryPostProcessor对BeanFactory
进行增强,最后才会去创建bean对象。
通过BeanFactoryPostProcessor对BeanFactory进行增强,主要是通过PostProcessorRegistrationDelegate的静态方法来完成的。在这过程中就会调用到ConfigurationClassPostProcessor这个类。
由于ConfigurationClassPostProcessor实现了BeanDefinitionRegistryPostProcessor接口,PostProcessorRegistrationDelegate就会调用ConfigurationClassPostProcessor的postProcessBeanDefinitionRegistry方法中,就会调用到processConfigBeanDefinitions方法来查找bean的定义。我们就从这里作为入口来看吧。
3.
下面我们就去看看ConfigurationClassPostProcessor的processConfigBeanDefinitions方法
/***Buildandvalidateaconfigurationmodelbasedontheregistryof*{@linkConfiguration}classes.*/publicvoidprocessConfigBeanDefinitions(BeanDefinitionRegistryregistry){List<BeanDefinitionHolder>configCandidates=newArrayList<>();String[]candidateNames=registry.getBeanDefinitionNames();//在下面的这个for循环中,会从beanFactory中已经有的bean的定义中寻找有Configuration注解的配置类。//默认这里获取到的只有一个包含SpringBootApplication注解的主类for(StringbeanName:candidateNames){......configCandidates.add(newBeanDefinitionHolder(beanDef,beanName));}}//如果没有找到配置类,就直接返回//Returnimmediatelyifno@Configurationclasseswerefoundif(configCandidates.isEmpty()){return;}......//在这里就通过ConfigurationClassParser去解析配置类//Parseeach@ConfigurationclassConfigurationClassParserparser=newConfigurationClassParser(this.metadataReaderFactory,this.problemReporter,this.environment,this.resourceLoader,this.componentScanBeanNameGenerator,registry);Set<BeanDefinitionHolder>candidates=newLinkedHashSet<>(configCandidates);Set<ConfigurationClass>alreadyParsed=newHashSet<>(configCandidates.size());do{StartupStepprocessConfig=this.applicationStartup.start("spring.context.config-classes.parse");//所有bean的定义的查找都是在这里完成的。下面我们去看看这里的parse方法parser.parse(candidates);......}
在ConfigurationClassParser中的parse方法中,由于我们的配置类是通过注解来定义的,所以会走AnnotatedBeanDefinition这个分支。继续会调用到processConfigurationClass(new ConfigurationClass(metadata, beanName), DEFAULT_EXCLUSION_FILTER);
这句,我们就直接进到这个processConfigurationClass方法去看吧。
protectedvoidprocessConfigurationClass(ConfigurationClassconfigClass,Predicate<String>filter)throwsIOException{//在这里首先看配置类上是否有Conditional注解,如果有的话,就去解析处理,看看是否要跳过这个注解类if(this.conditionEvaluator.shouldSkip(configClass.getMetadata(),ConfigurationPhase.PARSE_CONFIGURATION)){return;}//所有解析出来的配置类都要放置到configurationClasses中,key是当前解析出来的配置类,value就是表示这个配置类是通过谁来导入的。//如果这个配置类不是通过别的类来导入的,这时key和value就是一样的。ConfigurationClassexistingClass=this.configurationClasses.get(configClass);//如果通过多个配置类导入了同一个配置类,那么把这个和配置类的导入关系就要进行一下合并if(existingClass!=null){if(configClass.isImported()){if(existingClass.isImported()){existingClass.mergeImportedBy(configClass);}//Otherwiseignorenewimportedconfigclass;existingnon-importedclassoverridesit.return;}else{//Explicitbeandefinitionfound,probablyreplacinganimport.//Let'sremovetheoldoneandgowiththenewone.this.configurationClasses.remove(configClass);this.knownSuperclasses.values().removeIf(configClass::equals);}}//Recursivelyprocesstheconfigurationclassanditssuperclasshierarchy.//这里是将配置类转化为SourceClass对象SourceClasssourceClass=asSourceClass(configClass,filter);do{//在这里就会进行真正的配置类的解析出来。//注意这里是个do-while循环,处理完当前的配置类,会继续去处理当前配置类的父类。//如果当前类的父类类名不是java开头,且没有被处理过的话,就会在这个do-while循环中继续去处理sourceClass=doProcessConfigurationClass(configClass,sourceClass,filter);}while(sourceClass!=null);this.configurationClasses.put(configClass,configClass);}
this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)
这个的过滤主要是通过org.springframework.context.annotation.Condition
接口的子类去实现matches方法完成的。
举个例子简单说下:
@Configuration(proxyBeanMethods=false)@ConditionalOnMissingBean(name=AbstractApplicationContext.MESSAGE_SOURCE_BEAN_NAME,search=SearchStrategy.CURRENT)@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)@Conditional(ResourceBundleCondition.class)@EnableConfigurationPropertiespublicclassMessageSourceAutoConfiguration
上面是MessageSourceAutoConfiguration类的定义,首先会查找它上面的Conditional注解,会找到两个注解:
@ConditionalOnMissingBean(name = AbstractApplicationContext.MESSAGE_SOURCE_BEAN_NAME, search = SearchStrategy.CURRENT)
由于这个这个注解上面有@Conditional(OnBeanCondition.class)
,所以会交给OnBeanCondition这个类去处理。
@Conditional(ResourceBundleCondition.class)
,则会交给ResourceBundleCondition这个类去处理。
processConfigurationClass这个方法会有多个地方,主要会出现在三个地方:
就是调用parse方法的时候会调用到这个processConfigurationClass方法。
在doProcessConfigurationClass中解析当前配置类的属性时也可能会多次调用到processConfigurationClass方法。
在this.deferredImportSelectorHandler.process()调用时也可能会调用到processConfigurationClass方法
我们这里解析的所有配置类都添加到都会调用到configurationClasses.put(configClass, configClass)方法,所以我们最终有多个类添加到configurationClasses集合中,就至少有多少次调用到processConfigurationClass方法(有Conditional注解的判断,所以调用次数可能多于最终添加到configurationClasses集合中元素个数)
@NullableprotectedfinalSourceClassdoProcessConfigurationClass(ConfigurationClassconfigClass,SourceClasssourceClass,Predicate<String>filter)throwsIOException{//在这里,查看类是否有Component注解,有的话,查找当前类的内部类,进行处理if(configClass.getMetadata().isAnnotated(Component.class.getName())){//Recursivelyprocessanymember(nested)classesfirst//这里就可能会递归调用到上面的processConfigurationClass方法processMemberClasses(configClass,sourceClass,filter);}//Processany@PropertySourceannotations//在这里,查看类是否有PropertySources注解,有的话去解析属性配置,添加到环境上下文中去for(AnnotationAttributespropertySource:AnnotationConfigUtils.attributesForRepeatable(sourceClass.getMetadata(),PropertySources.class,org.springframework.context.annotation.PropertySource.class)){if(this.environmentinstanceofConfigurableEnvironment){processPropertySource(propertySource);}else{logger.info("Ignoring@PropertySourceannotationon["+sourceClass.getMetadata().getClassName()+"].Reason:EnvironmentmustimplementConfigurableEnvironment");}}//Processany@ComponentScanannotations//在这里,查看类是否有ComponentScans注解,有的话,就根据这里的条件去进行目录扫描,查找bean的定义//由于我们当前的类上有SpringBootApplication注解,所以这里是能够找到ComponentScan注解的,就会进到这个方法里面去Set<AnnotationAttributes>componentScans=AnnotationConfigUtils.attributesForRepeatable(sourceClass.getMetadata(),ComponentScans.class,ComponentScan.class);if(!componentScans.isEmpty()&&!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(),ConfigurationPhase.REGISTER_BEAN)){for(AnnotationAttributescomponentScan:componentScans){//Theconfigclassisannotatedwith@ComponentScan->performthescanimmediately//在这里,就会去处理ComponentScans注解相关的内容。//ComponentScans注解上有个basePackages属性,用来指定扫描的包的名字。//如果没有指定basePackages属性,就在当前类的包下及其所有子包下去查找相关的bean的定义。//我们一般不会指定basePackages属性,那么会在当前sourceClass类的包及其所有子包下去查找bean的定义。//我们自己代码中定义的controller,service,dao等等都是在这一步获取到bean的定义的。Set<BeanDefinitionHolder>scannedBeanDefinitions=this.componentScanParser.parse(componentScan,sourceClass.getMetadata().getClassName());//Checkthesetofscanneddefinitionsforanyfurtherconfigclassesandparserecursivelyifneededfor(BeanDefinitionHolderholder:scannedBeanDefinitions){BeanDefinitionbdCand=holder.getBeanDefinition().getOriginatingBeanDefinition();if(bdCand==null){bdCand=holder.getBeanDefinition();}if(ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand,this.metadataReaderFactory)){//这里也会间接调用到processConfigurationClass方法parse(bdCand.getBeanClassName(),holder.getBeanName());}}}}//Processany@Importannotations//在这里,就会去处理类上的import注解。//getImports(sourceClass)首先会获取到import的类。//这里会有两个,一个是AutoConfigurationPackage上注解的AutoConfigurationPackages.Registrar.class//另一个是EnableAutoConfiguration上的注解AutoConfigurationImportSelector.class)//下面我们去看看processImports这个方法//这里面也可能会调用到processConfigurationClass方法processImports(configClass,sourceClass,getImports(sourceClass),filter,true);......}
doProcessConfigurationClass是真正用来处理配置类的。
在这个方法中会依次处理内部类、PropertySources注解、ComponentScans注解、Import注解、ImportResource注解、Bean注解、接口上的默认方法、继续递归到它的父类。
其中:
内部类会继续调用processConfigurationClass方法递归去处理
PropertySources注解解析后添加到环境上下文中
ComponentScans注解扫描到的到的类会直接被添加到beanFactory中,也会继续调用processConfigurationClass方法递归去处理
Import注解会分3种情况处理:
Import的类如果实现了ImportSelector。且实现了它的子接口DeferredImportSelector,则会添加到deferredImportSelectors中,后续进行处理。如果没有实现子接口,就递归调用processImports进行处理。
Import的类如果实现了ImportBeanDefinitionRegistrar。则添加到当前配置类的属性中,进行后续处理。
不属于上面两种情况的话,就继续递归调用processConfigurationClass进行处理。
ImportResource注解、Bean注解、接口上的默认方法这些都会解析后添加到当前配置类的属性上,后续进行处理
对下面方法的几个入参简单描述下:
configClass,currentSourceClass这两个参数直接都是指代我们包含SpringBootApplication注解的主类。
其中configClass表示当前处理的类是被谁导入的,currentSourceClass表示当前正在处理的类。这两者一般底层是同一个资源类,但是有可能会有递归调用,这时两者就可能会不同。importCandidates是通过import注解导入的类,这里是
AutoConfigurationPackages.Registrar.class
和AutoConfigurationImportSelector.class importCandidates
就是当前被导入的类,也就是在这里被处理的类exclusionFilter是在ConfigurationClassParser中定义的,用来过滤
java.lang.annotation.
和org.springframework.stereotype.
开头的注解checkForCircularImports表示是否检查递归导入
privatevoidprocessImports(ConfigurationClassconfigClass,SourceClasscurrentSourceClass,Collection<SourceClass>importCandidates,Predicate<String>exclusionFilter,booleancheckForCircularImports){if(importCandidates.isEmpty()){return;}//这里是错误检查,检查是否出现了递归if(checkForCircularImports&&isChainedImportOnStack(configClass)){this.problemReporter.error(newCircularImportProblem(configClass,this.importStack));}else{//先将当前的配置类压入栈this.importStack.push(configClass);try{//这里,就会对import标签导入的类进行处理for(SourceClasscandidate:importCandidates){//AutoConfigurationImportSelector.class类就会走下面的分支if(candidate.isAssignable(ImportSelector.class)){//CandidateclassisanImportSelector->delegatetoittodetermineimportsClass<?>candidateClass=candidate.loadClass();//首先在这里创建一个AutoConfigurationImportSelector类的对象,ImportSelectorselector=ParserStrategyUtils.instantiateClass(candidateClass,ImportSelector.class,this.environment,this.resourceLoader,this.registry);Predicate<String>selectorFilter=selector.getExclusionFilter();if(selectorFilter!=null){exclusionFilter=exclusionFilter.or(selectorFilter);}if(selectorinstanceofDeferredImportSelector){//在这里,将当前的配置类和AutoConfigurationImportSelector的对象封装成DeferredImportSelectorHolder对象//添加到延迟导入的集合deferredImportSelectors中this.deferredImportSelectorHandler.handle(configClass,(DeferredImportSelector)selector);}else{String[]importClassNames=selector.selectImports(currentSourceClass.getMetadata());Collection<SourceClass>importSourceClasses=asSourceClasses(importClassNames,exclusionFilter);processImports(configClass,currentSourceClass,importSourceClasses,exclusionFilter,false);}}//AutoConfigurationPackages.Registrar.class这个类就会走到这个分支中//在这个分支中,首先创建AutoConfigurationPackages.Registrar的对象//添加到当前配置类的importBeanDefinitionRegistrars属性中去elseif(candidate.isAssignable(ImportBeanDefinitionRegistrar.class)){//CandidateclassisanImportBeanDefinitionRegistrar->//delegatetoittoregisteradditionalbeandefinitionsClass<?>candidateClass=candidate.loadClass();ImportBeanDefinitionRegistrarregistrar=ParserStrategyUtils.instantiateClass(candidateClass,ImportBeanDefinitionRegistrar.class,this.environment,this.resourceLoader,this.registry);configClass.addImportBeanDefinitionRegistrar(registrar,currentSourceClass.getMetadata());}else{//CandidateclassnotanImportSelectororImportBeanDefinitionRegistrar->//processitasan@Configurationclassthis.importStack.registerImport(currentSourceClass.getMetadata(),candidate.getMetadata().getClassName());processConfigurationClass(candidate.asConfigClass(configClass),exclusionFilter);}}}catch(BeanDefinitionStoreExceptionex){throwex;}catch(Throwableex){thrownewBeanDefinitionStoreException("Failedtoprocessimportcandidatesforconfigurationclass["+configClass.getMetadata().getClassName()+"]",ex);}finally{this.importStack.pop();}}}
上面的import导入类处理完了,下面我们继续回到doProcessConfigurationClass中去看剩余的部分
@NullableprotectedfinalSourceClassdoProcessConfigurationClass(ConfigurationClassconfigClass,SourceClasssourceClass,Predicate<String>filter)throwsIOException{......//这部分前面已经分析过了,我们就继续看后面的吧//Processany@ImportResourceannotations//这里是处理ImportResource注解AnnotationAttributesimportResource=AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(),ImportResource.class);if(importResource!=null){String[]resources=importResource.getStringArray("locations");Class<?extendsBeanDefinitionReader>readerClass=importResource.getClass("reader");for(Stringresource:resources){StringresolvedResource=this.environment.resolveRequiredPlaceholders(resource);configClass.addImportedResource(resolvedResource,readerClass);}}//Processindividual@Beanmethods//这里是处理配置类内部的有Bean注解的方法,添加到配置类的beanMethods属性中Set<MethodMetadata>beanMethods=retrieveBeanMethodMetadata(sourceClass);for(MethodMetadatamethodMetadata:beanMethods){configClass.addBeanMethod(newBeanMethod(methodMetadata,configClass));}//Processdefaultmethodsoninterfaces//这里处理配置类实现的接口上默认方法上有Bean注解的话,也添加到beanMethods属性中processInterfaces(configClass,sourceClass);//Processsuperclass,ifany//这里去获取配置类的父类,如果存在父类且父类类名不是java开头且还没有被处理过,就会返回父类,继续进行父类的处理。if(sourceClass.getMetadata().hasSuperClass()){Stringsuperclass=sourceClass.getMetadata().getSuperClassName();if(superclass!=null&&!superclass.startsWith("java")&&!this.knownSuperclasses.containsKey(superclass)){this.knownSuperclasses.put(superclass,configClass);//Superclassfound,returnitsannotationmetadataandrecursereturnsourceClass.getSuperClass();}}//Nosuperclass->processingiscompletereturnnull;}
到这里processConfigurationClass方法就整个分析完了。
下面就会走到parse方法的最后一句了。我们进去看看
publicvoidparse(Set<BeanDefinitionHolder>configCandidates){......//就会走到下面这行代码this.deferredImportSelectorHandler.process();}
这里主要是对延迟导入的类进行处理
publicvoidprocess(){//在上面代码中我们分析到this.deferredImportSelectors中只有一个//由前面的配置类和AutoConfigurationImportSelector类的对象封装的DeferredImportSelectorHolder对象List<DeferredImportSelectorHolder>deferredImports=this.deferredImportSelectors;this.deferredImportSelectors=null;try{if(deferredImports!=null){DeferredImportSelectorGroupingHandlerhandler=newDeferredImportSelectorGroupingHandler();deferredImports.sort(DEFERRED_IMPORT_COMPARATOR);//这里会对延迟导入的类进行分组,添加到handler中,由于我们这里只有一个对象,所以这块的分组,我们可以不用太关注//同时会将前面的配置类添加到handler对象的configurationClasses属性中deferredImports.forEach(handler::register);//下面就会交给handler去进行处理handler.processGroupImports();}}finally{this.deferredImportSelectors=newArrayList<>();}}}
下面我们看看processGroupImports是如何处理的
publicvoidprocessGroupImports(){//这里就按分组去处理了for(DeferredImportSelectorGroupinggrouping:this.groupings.values()){Predicate<String>exclusionFilter=grouping.getCandidateFilter();//这里的grouping.getImports()就回去获取系统的配置类,我们下面去看这个getImportsgrouping.getImports().forEach(entry->{......}}
这里的grouping.getCandidateFilter()
来自两部分:
另一部分是来自ConfigurationClassParser定义的lambda表达式
这个是在ConfigurationClassParser类的一个静态内部类DeferredImportSelectorGrouping中的方法
publicIterable<Group.Entry>getImports(){//这里的deferredImports中只有一个对象,还是之前的DeferredImportSelectorHolderfor(DeferredImportSelectorHolderdeferredImport:this.deferredImports){//这里的this.group就是之前分组的deferredImport.getImportSelector().getImportGroup();方法的返回值创建的对象//具体就是AutoConfigurationImportSelector.AutoConfigurationGroup的对象//下面我们先看看这个process方法this.group.process(deferredImport.getConfigurationClass().getMetadata(),deferredImport.getImportSelector());}returnthis.group.selectImports();}
process是在AutoConfigurationImportSelector.AutoConfigurationGroup这个类中
publicvoidprocess(AnnotationMetadataannotationMetadata,DeferredImportSelectordeferredImportSelector){Assert.state(deferredImportSelectorinstanceofAutoConfigurationImportSelector,()->String.format("Only%simplementationsaresupported,got%s",AutoConfigurationImportSelector.class.getSimpleName(),deferredImportSelector.getClass().getName()));//下面这行代码也比较重要,我们进去看看AutoConfigurationEntryautoConfigurationEntry=((AutoConfigurationImportSelector)deferredImportSelector).getAutoConfigurationEntry(annotationMetadata);this.autoConfigurationEntries.add(autoConfigurationEntry);for(StringimportClassName:autoConfigurationEntry.getConfigurations()){this.entries.putIfAbsent(importClassName,annotationMetadata);}}
protectedAutoConfigurationEntrygetAutoConfigurationEntry(AnnotationMetadataannotationMetadata){//这里,我们就能看到设置spring.boot.enableautoconfiguration属性去禁止导入系统配置的bean的定义if(!isEnabled(annotationMetadata)){returnEMPTY_ENTRY;}AnnotationAttributesattributes=getAttributes(annotationMetadata);//在下面这行中,就能看到通过ClassLoader去加载META-INF/spring.factories文件,读取内容。放置到cache中//在当前这里,会去获取key=org.springframework.boot.autoconfigure.EnableAutoConfiguration的所有属性配置List<String>configurations=getCandidateConfigurations(annotationMetadata,attributes);configurations=removeDuplicates(configurations);Set<String>exclusions=getExclusions(annotationMetadata,attributes);checkExcludedClasses(configurations,exclusions);configurations.removeAll(exclusions);//在这里获取配置过滤类并创建对象,对上面的configuras进行过滤//这里的配置过滤类也是从cache中获取,key=org.springframework.boot.autoconfigure.AutoConfigurationImportFilterconfigurations=getConfigurationClassFilter().filter(configurations);//这行代码不关键,我们可以不用去关注fireAutoConfigurationImportEvents(configurations,exclusions);//这里返回一个AutoConfigurationEntry对象//其中configurations是过滤器能够匹配到的配置类,exclusions在我们这里是空的returnnewAutoConfigurationEntry(configurations,exclusions);}
上面代码中getConfigurationClassFilter()获取到的是:
是来自spring.factories文件中的org.springframework.boot.autoconfigure.AutoConfigurationImportFilter
org.springframework.boot.autoconfigure.condition.OnClassCondition
这个类主要检查是否存在指定的类
org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition
这个类主要检查是否存在WebApplicationContext.
org.springframework.boot.autoconfigure.condition.OnBeanCondition
这个类主要检查是否存在指定的bean
在这个过程中,在生成filter过程中,首先会通过类加载器去读取META-INF/spring-autoconfigure-metadata.properties
这些文件。
在这里,主要是通过类名.ConditionalOnBean、类名.ConditionalOnSingleCandidate、类名.ConditionalOnClass、类名.ConditionalOnWebApplication来过滤掉不符合的配置类。
具体的算法入口都在这3个类的父类FilteringSpringBootCondition的match方法,具体的实现入口分别在这3个类的getOutcomes方法中。
由于这3个类都是实现了Condition接口,因此前面分析的 processConfigurationClass方法开始的地方通过 Conditional注解过滤配置类也会用到这3个类。
从上面也可以看出springboot的按需加载主要也是通过实现Condition接口来完成的。
再回到process这个方法。
publicvoidprocess(AnnotationMetadataannotationMetadata,DeferredImportSelectordeferredImportSelector){......//上面的代码刚才已经分析过了//在这里将上面返回的AutoConfigurationEntry对象添加到autoConfigurationEntries中this.autoConfigurationEntries.add(autoConfigurationEntry);for(StringimportClassName:autoConfigurationEntry.getConfigurations()){//分别将添加的配置类添加到entries这个属性中//importClassName是新查找到的配置类,annotationMetadata都是同一个就是我们的主类this.entries.putIfAbsent(importClassName,annotationMetadata);}}
在接下来的selectImports方法中,首先会对这些新添加的配置类进行排序,然后组装成new Entry(this.entries.get(importClassName), importClassName))
对象的集合。
这里需要注意的是this.entries.get(importClassName)
这就是我们的主类,importClassName是我们需要添加的配置类。
这里主要是为了对当前导入的配置类和它是被谁导入的进行一个关联(在这里,所有要导入的配置类都是由我们的主类来导入的)。
就是在后面创建ConfigurationClass对象时会使用public ConfigurationClass(MetadataReader metadataReader, @Nullable ConfigurationClass importedBy)
这个构造方法。
最后在添加这些配置类到beanFactory中时通过
下面再回到processGroupImports方法
publicvoidprocessGroupImports(){for(DeferredImportSelectorGroupinggrouping:this.groupings.values()){Predicate<String>exclusionFilter=grouping.getCandidateFilter();//上面已经分析到grouping.getImports()返回的是Entry对象的集合grouping.getImports().forEach(entry->{//entry.getMetadata()返回的还是我们之前的主类。//这里的configurationClass也是我们之前的主类。//这个主要是为了在processImports方法中创建的配置类为它们设置importedBy属性ConfigurationClassconfigurationClass=this.configurationClasses.get(entry.getMetadata());try{//这里又会调用到processImports这个方法。这个在前面已经分析过了,但是这里有一点不一样,下面我们看看不一样的地方processImports(configurationClass,asSourceClass(configurationClass,exclusionFilter),Collections.singleton(asSourceClass(entry.getImportClassName(),exclusionFilter)),exclusionFilter,false);}catch(BeanDefinitionStoreExceptionex){throwex;}catch(Throwableex){thrownewBeanDefinitionStoreException("Failedtoprocessimportcandidatesforconfigurationclass["+configurationClass.getMetadata().getClassName()+"]",ex);}});}}
关于这个processImports方法的参数前面有描述,这里就不再说了
下面的这个方法中这时importCandidates和之前的有点不一样,之前的是通过import注解导入的分别会走for循环的前面两个分支,现在大概率会走到后面的else分支
privatevoidprocessImports(ConfigurationClassconfigClass,SourceClasscurrentSourceClass,Collection<SourceClass>importCandidates,Predicate<String>exclusionFilter,booleancheckForCircularImports){this.importStack.push(configClass);try{for(SourceClasscandidate:importCandidates){if(candidate.isAssignable(ImportSelector.class)){......}elseif(candidate.isAssignable(ImportBeanDefinitionRegistrar.class)){......}else{//CandidateclassnotanImportSelectororImportBeanDefinitionRegistrar->//processitasan@Configurationclass//上次进入这个方法,分别走了上面的两个分支,现在大概率会走到这个分支//这里会将导入的类添加到imports属性中,key是新导入的配置类,value是我们之前的主类this.importStack.registerImport(currentSourceClass.getMetadata(),candidate.getMetadata().getClassName());//这里又会去处理新添加的配置类,在这里是有可能出现递归的,下面我们具体分析下这里的处理逻辑processConfigurationClass(candidate.asConfigClass(configClass),exclusionFilter);}}}......}}
在上面的processImports方法中,会处理新添加的配置类,会调用到processConfigurationClass这个方法。
到上面为止,ConfigurationClassPostProcessor的processConfigBeanDefinitions方法从parse处理的部分就全部分析完了 。
这部分主要是处理了通过主类上面的注解,将所有的配置类都添加到ConfigurationClassParser类的成员变量configurationClasses中。对于配置类上的ImportResource、Bean等等则添加配置类的对应的属性上。
这里需要注意的是在整个整个过程中只有ComponentScans扫描到的配置类会添加到beanFactory中。
下面我们继续看看后面的代码。
publicvoidprocessConfigBeanDefinitions(BeanDefinitionRegistryregistry){......do{StartupStepprocessConfig=this.applicationStartup.start("spring.context.config-classes.parse");parser.parse(candidates);//前面已经分析到了这里parser.validate();//这里就会得到所有的配置类Set<ConfigurationClass>configClasses=newLinkedHashSet<>(parser.getConfigurationClasses());//alreadyParsed第一次是空的,由于这个方法是do-while循环,在后面会对这个变量赋值configClasses.removeAll(alreadyParsed);//Readthemodelandcreatebeandefinitionsbasedonitscontentif(this.reader==null){this.reader=newConfigurationClassBeanDefinitionReader(registry,this.sourceExtractor,this.resourceLoader,this.environment,this.importBeanNameGenerator,parser.getImportRegistry());}//在这里就会对前面获取的所有的配置类添加到beanFactory中this.reader.loadBeanDefinitions(configClasses);alreadyParsed.addAll(configClasses);processConfig.tag("classCount",()->String.valueOf(configClasses.size())).end();candidates.clear();//这里就是对比前后beanFactory中的beanDefinition数量是否有增加,如果有增加说明我们在本次do-while代码中添加了beanFactory//下面的逻辑主要是为了判断当前扫描出来的配置类是否全部添加进了beanFactory中,如果有配置类还没有被今天进去,就会循环,重新执行上面的逻辑if(registry.getBeanDefinitionCount()>candidateNames.length){String[]newCandidateNames=registry.getBeanDefinitionNames();Set<String>oldCandidateNames=newHashSet<>(Arrays.asList(candidateNames));Set<String>alreadyParsedClasses=newHashSet<>();for(ConfigurationClassconfigurationClass:alreadyParsed){alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());}for(StringcandidateName:newCandidateNames){if(!oldCandidateNames.contains(candidateName)){BeanDefinitionbd=registry.getBeanDefinition(candidateName);if(ConfigurationClassUtils.checkConfigurationClassCandidate(bd,this.metadataReaderFactory)&&!alreadyParsedClasses.contains(bd.getBeanClassName())){candidates.add(newBeanDefinitionHolder(bd,candidateName));}}}candidateNames=newCandidateNames;}}while(!candidates.isEmpty());//RegistertheImportRegistryasabeaninordertosupportImportAware@Configurationclassesif(sbr!=null&&!sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)){sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME,parser.getImportRegistry());}if(this.metadataReaderFactoryinstanceofCachingMetadataReaderFactory){//ClearcacheinexternallyprovidedMetadataReaderFactory;thisisano-op//forasharedcachesinceit'llbeclearedbytheApplicationContext.((CachingMetadataReaderFactory)this.metadataReaderFactory).clearCache();}}
上面的其他代码都比较简单,我们下面主要对上面的this.reader.loadBeanDefinitions(configClasses);
做个简单分析吧。
ConfigurationClassBeanDefinitionReader的方法
publicvoidloadBeanDefinitions(Set<ConfigurationClass>configurationModel){//这个类还是用来对Conditional注解进行处理,来判断当前配置类是否要被过滤掉TrackedConditionEvaluatortrackedConditionEvaluator=newTrackedConditionEvaluator();for(ConfigurationClassconfigClass:configurationModel){//在这里会对每个配置类及它的属性进行处理,封装成beanDefinition添加到beanFactory中去loadBeanDefinitionsForConfigurationClass(configClass,trackedConditionEvaluator);}}
privatevoidloadBeanDefinitionsForConfigurationClass(ConfigurationClassconfigClass,TrackedConditionEvaluatortrackedConditionEvaluator){//这里就会对Conditional注解进行判断,如果当前类是被导入的,就会去判断导入它的类if(trackedConditionEvaluator.shouldSkip(configClass)){StringbeanName=configClass.getBeanName();if(StringUtils.hasLength(beanName)&&this.registry.containsBeanDefinition(beanName)){this.registry.removeBeanDefinition(beanName);}this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName());return;}//如果类是被导入的,就会去对它进行处理if(configClass.isImported()){registerBeanDefinitionForImportedConfigurationClass(configClass);}//下面就是对配置类的各种属性进行处理//处理方法上的bean注解for(BeanMethodbeanMethod:configClass.getBeanMethods()){loadBeanDefinitionsForBeanMethod(beanMethod);}//处理导入的资源loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());//处理导入的ImportBeanDefinitionRegistrarloadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());}
在上面的代码也可以看到,单纯的配置类,如果configClass.isImported()
返回false,就不会被添加到beanFactory中。也就是如果配置类不是被导入的,就不会将配置类添加到beanFactory中。
前面说过ComponentScans扫描到的类在处理过程中就被添加到了beanFactory中,其他的配置类都是在上面的方法中被添加进去的。
所有添加的类大致可以分为两部分:
通过类上的注解,直接被添加到配置类中。这部分配置类它们的被导入类就是当前的主类。另一部分是通过主类上的@Import(AutoConfigurationImportSelector.class)
注解,读取META-INF/spring.factories
文件,经过META-INF/spring-autoconfigure-metadata.properties
文件过滤后被处理的类。
上面两部分处理的时候都会进行递归,一层一层处理。而且所有的处理过程中也都会根据 Conditional注解进行过滤。
同时也需要注意虽然添加到beanFactory中的都是beanD,但是具体都是不一样的。比如:
ScannedGenericBeanDefinition是通过ComponentScans注解添加的
ConfigurationClassBeanDefinition是处理方法上的bean注解添加的
AnnotatedGenericBeanDefinition是其他普通的配置类
感谢你能够认真阅读完这篇文章,希望小编分享的“springboot如何自动扫描添加BeanDefinition源码”这篇文章对大家有帮助,同时也希望大家多多支持恰卡编程网,关注恰卡编程网行业资讯频道,更多相关知识等着你来学习!
推荐阅读
-
springboot实现基于aop的切面日志
本文实例为大家分享了springboot实现基于aop的切面日志的具体代码,供大家参考,具体内容如下通过aop的切面方式实现日志...
-
SpringBoot定时任务功能怎么实现
-
SpringBoot中的@Import注解怎么使用
-
SpringBoot整合Lombok及常见问题怎么解决
-
springboot图片验证码功能模块怎么实现
-
Springboot+SpringSecurity怎么实现图片验证码登录
-
SpringBoot注解的知识点有哪些
SpringBoot注解的知识点有哪些这篇“SpringBoot注...
-
SpringBoot2.x中management.security.enabled=false无效怎么解决
-
springboot怎么禁用某项健康检查
springboot怎么禁用某项健康检查今天小编给大家分享一下sp...
-
SpringBoot2怎么自定义端点