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
在ConfigurationClassParser中的parse方法中,由于我们的配置类是通过注解来定义的,所以会走AnnotatedBeanDefinition这个分支。继续会调用到processConfigurationClass(new ConfigurationClass(metadata, beanName), DEFAULT_EXCLUSION_FILTER);
这句,我们就直接进到这个processConfigurationClass方法去看吧。
protectedvoidprocessConfigurationClass(ConfigurationClassconfigClass,Predicate
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
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
上面的import导入类处理完了,下面我们继续回到doProcessConfigurationClass中去看剩余的部分
@NullableprotectedfinalSourceClassdoProcessConfigurationClass(ConfigurationClassconfigClass,SourceClasssourceClass,Predicate
到这里processConfigurationClass方法就整个分析完了。
下面就会走到parse方法的最后一句了。我们进去看看
publicvoidparse(Set
这里主要是对延迟导入的类进行处理
publicvoidprocess(){//在上面代码中我们分析到this.deferredImportSelectors中只有一个//由前面的配置类和AutoConfigurationImportSelector类的对象封装的DeferredImportSelectorHolder对象List
下面我们看看processGroupImports是如何处理的
publicvoidprocessGroupImports(){//这里就按分组去处理了for(DeferredImportSelectorGroupinggrouping:this.groupings.values()){Predicate
这里的grouping.getCandidateFilter()
来自两部分:
另一部分是来自ConfigurationClassParser定义的lambda表达式
这个是在ConfigurationClassParser类的一个静态内部类DeferredImportSelectorGrouping中的方法
publicIterable
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
上面代码中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
关于这个processImports方法的参数前面有描述,这里就不再说了
下面的这个方法中这时importCandidates和之前的有点不一样,之前的是通过import注解导入的分别会走for循环的前面两个分支,现在大概率会走到后面的else分支
privatevoidprocessImports(ConfigurationClassconfigClass,SourceClasscurrentSourceClass,Collection
在上面的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
上面的其他代码都比较简单,我们下面主要对上面的this.reader.loadBeanDefinitions(configClasses);
做个简单分析吧。
ConfigurationClassBeanDefinitionReader的方法
publicvoidloadBeanDefinitions(Set
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源码”这篇文章对大家有帮助,同时也希望大家多多支持恰卡编程网,关注恰卡编程网行业资讯频道,更多相关知识等着你来学习!
推荐阅读
-
vue动态添加删除输入框(springboot vue怎么让数据库显示出来)
springbootvue怎么让数据库显示出来?一般情况下是前端调阅后端接口,来获取到数据库的数据,后端哪里会把数据库的数据整理...
-
springboot实现基于aop的切面日志
本文实例为大家分享了springboot实现基于aop的切面日志的具体代码,供大家参考,具体内容如下通过aop的切面方式实现日志...
-
SpringBoot定时任务功能怎么实现
-
SpringBoot中的@Import注解怎么使用
-
SpringBoot整合Lombok及常见问题怎么解决
SpringBoot整合Lombok及常见问题怎么解决这篇文章主要...
-
springboot图片验证码功能模块怎么实现
springboot图片验证码功能模块怎么实现本篇内容主要讲解“s...
-
Springboot+SpringSecurity怎么实现图片验证码登录
-
SpringBoot注解的知识点有哪些
SpringBoot注解的知识点有哪些这篇“SpringBoot注...
-
SpringBoot2.x中management.security.enabled=false无效怎么解决
SpringBoot2.x中management.security.enabled=false无效怎么解决...
-
springboot怎么禁用某项健康检查
springboot怎么禁用某项健康检查今天小编给大家分享一下sp...