這篇文章主要分析一下這幾個注解的原理。
SpringBoot中這幾個注解關(guān)系比較緊密诫舅,少了@SpringBootApplication
注解羽利,SpringBoot很多功能都沒法使用,所以文章分析的內(nèi)容涉及了該注解
另外還有個問題也與@SpringBootApplication
有關(guān):
- SpringBoot為什么不需要配置包掃描刊懈,Spring是如何知道要掃描哪些路徑下的類这弧?
1 Demo
在上一篇文章上,加上兩個類虚汛,Bean1和MyConfiguration匾浪,代碼如下:
@Configuration
public class MyConfiguration {
@Bean
public Bean1 bean1(){
return new Bean1();
}
}
public class Bean1 {
public Bean1(){
System.out.println("bean init");
}
}
上面代碼的作用是實例化一個Bean1對象(作用類似在Bean1類上加上@Component注解)。
這時候工程目錄如下:
下面就以上面代碼為例子卷哩,分析@Configuration和@Bean注解的原理蛋辈。
2 @SpringBootApplication
在分析之前,需要先介紹一下啟動類的@SpringBootApplication
注解将谊,先看下其定義
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
@AliasFor(annotation = EnableAutoConfiguration.class)
Class<?>[] exclude() default {};
@AliasFor(annotation = EnableAutoConfiguration.class)
String[] excludeName() default {};
@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
String[] scanBasePackages() default {};
@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
Class<?>[] scanBasePackageClasses() default {};
}
@SpringBootApplication
注解上很多個注解冷溶,其中就包括@ComponentScan
注解,所以這里查詢@ComponentScan
注解是能成功的瓢娜,因為這里及上文中查詢@Configuration
的方式都是:查找當前注解挂洛,如果注解沒有則查找注解上的注解有沒礼预,所以一個@SpringBootApplication
就包含了@ComponentScan
眠砾、@SpringBootConfiguration
(即@Configuration)、@EnableAutoConfiguration
幾個注解的功能(當然也可以不使用該注解,直接把該注解上的其他注解直接放到啟動類上)
在后續(xù)的處理當中褒颈,當判斷一個類上是否有某個注解的時候柒巫,即使一個類是間接依賴的某個注解,那么這種情況也是符合的谷丸,也會將該注解的信息給提取出來堡掏,例如,判斷啟動類是否擁有@Configuration
注解刨疼,流程如下:
- 首先判斷啟動類上是是否有有
@Configuration
注解泉唁,結(jié)果沒有 - 判斷
@SpringBootConfiguration
注解上是否有@Configuration
注解注解,結(jié)果沒有 - 獲取
@SpringBootConfiguration
注解上的所有注解揩慕,判斷這些注解上是否有@Configuration
注解亭畜,重復類似23的流程
由于@SpringBootConfiguration
注解上有個@SpringBootConfiguration
注解,而該注解上有@Configuration
注解迎卤,所以該查找成功
3 解析入口
上文分析了拴鸵,執(zhí)行run方法后,最后會調(diào)用到org.springframework.context.support.AbstractApplicationContext#refresh
方法蜗搔,這是Spring核心的方法劲藐,這篇文章主要分析幾個注解的實現(xiàn),所以這里只展示相關(guān)的代碼:
@Override
public void refresh() throws BeansException, IllegalStateException {
//這里就是核心邏輯的入口樟凄,主要是對BeanFactoryPostProcessor的處理
invokeBeanFactoryPostProcessors(beanFactory);
}
BeanFactoryPostProcessor:類似于BeanPostProcessor聘芜,BeanPostProcessor主要用來后置處理Bean的,而BeanFactoryPostProcessor則是用來在Bean初始化完成之前缝龄,用來操作BeanFactory的厉膀,兩者都是Spring開放的擴展點,用來擴展對應的功能
invokeBeanFactoryPostProcessors內(nèi)將邏輯委托給PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors
方法二拐,該方法內(nèi)部則會對所有的BeanFactoryPostProcessor進行排序并調(diào)用BeanFactoryPostProcessor接口對應的方法服鹅。
BeanFactoryPostProcessor具體處理這里不再詳細展開,只需要知道@Configuration處理邏輯在某個BeanFactoryPostProcessor中百新,這個類就是ConfigurationClassPostProcessor
找到該類企软,發(fā)現(xiàn)其實現(xiàn)的接口是BeanDefinitionRegistryPostProcessor而不是BeanFactoryPostProcessor,而BeanDefinitionRegistryPostProcessor的父接口是BeanFactoryPostProcessor饭望,和BeanFactoryPostProcessor其實差不多仗哨,如果實現(xiàn)了該接口,會先調(diào)用BeanDefinitionRegistryPostProcessor接口的postProcessBeanDefinitionRegistry方法铅辞,再調(diào)用BeanFactoryPostProcessor接口的postProcessBeanFactory方法
4 ConfigurationClassPostProcessor
ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry
方法就是核心所在厌漂,其中會調(diào)用到processConfigBeanDefinitions
方法,直接看下這個方法的邏輯(省略一些非核心流程代碼):
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
// 獲取當前注冊到容器的BeanDefinition的名稱集合
String[] candidateNames = registry.getBeanDefinitionNames();
// 篩選具有@Configuration注解信息的BeanDefinition
for (String beanName : candidateNames) {
BeanDefinition beanDef = registry.getBeanDefinition(beanName);
//判斷BeanDefinition的CONFIGURATION_CLASS_ATTRIBUTE屬性是否為full
//判斷BeanDefinition的CONFIGURATION_CLASS_ATTRIBUTE屬性是否為lite
if (ConfigurationClassUtils.isFullConfigurationClass(beanDef) ||
ConfigurationClassUtils.isLiteConfigurationClass(beanDef)) {
//log忽略
}
else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
// 該BeanDefinition對應的類是否有@Configuration注解
configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
}
}
// 如果當前沒有斟珊,那么就不需要進行@Configuration的處理
if (configCandidates.isEmpty()) {
return;
}
//用來解析各種注解的解析器
ConfigurationClassParser parser = new ConfigurationClassParser(
this.metadataReaderFactory, this.problemReporter, this.environment,
this.resourceLoader, this.componentScanBeanNameGenerator, registry);
Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
do {
parser.parse(candidates);//開始解析
parser.validate();//校驗
// 這是解析完成后苇倡,得到的需要加載到容器中的配置類
Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
configClasses.removeAll(alreadyParsed);
if (this.reader == null) {
this.reader = new ConfigurationClassBeanDefinitionReader(
registry, this.sourceExtractor, this.resourceLoader, this.environment,
this.importBeanNameGenerator, parser.getImportRegistry());
}
//將這些解析到的類加載到容器中
this.reader.loadBeanDefinitions(configClasses);
alreadyParsed.addAll(configClasses);
candidates.clear();
while (!candidates.isEmpty());
// Register the ImportRegistry as a bean in order to support ImportAware @Configuration classes
if (sbr != null) {
if (!sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {
sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());
}
}
}
processConfigBeanDefinitions整個方法可以大體劃分為三個階段:
- 從容器中獲取和Configuration有關(guān)系的BeanDefinition
- 以該BeanDefinition為起點,進行解析操作,得到解析結(jié)果集
- 將解析到的結(jié)果集加載到容器中旨椒,即構(gòu)造成一個BeanDefinition放到容器中待初始化
這里還有幾個注意點:
- 在上面的第1步的時候晓褪,能取到的數(shù)據(jù)為所有的BeanFactoryPostProcessor以及我們的main方法的類SpringBootDemoApplication。BeanFactoryPostProcessor當然不奇怪综慎,而為什么還有SpringBootDemoApplication呢涣仿?回顧一下上篇文章說的啟動流程,在load方法中把SpringBootDemoApplication已經(jīng)加載進容器了示惊。另外這個時候好港,configCandidates只有一個元素,即SpringBootDemoApplication
- 結(jié)合我們的demo米罚,可以猜測媚狰,第2步得到的結(jié)果集中,應該包括MyConfiguration阔拳,可能包括Bean1崭孤,如果這里沒包括的話可能是以別的形式獲取,這樣MyConfiguration和Bean1才能在后面Bean初始化的時候被創(chuàng)建糊肠,而具體怎么解析的辨宠,后續(xù)會分析到。
4.1 判斷類是否與@Configuration有關(guān)
在上面第1步中货裹,有@Configuration注解的會加入到集合當中嗤形,這個判斷是在ConfigurationClassUtils#checkConfigurationClassCandidate
當中實現(xiàn)
public static boolean checkConfigurationClassCandidate(BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) {
String className = beanDef.getBeanClassName();
if (className == null || beanDef.getFactoryMethodName() != null) {
return false;
}
//獲取注解元數(shù)據(jù)信息
AnnotationMetadata metadata;
if (beanDef instanceof AnnotatedBeanDefinition &&
className.equals(((AnnotatedBeanDefinition) beanDef).getMetadata().getClassName())) {
metadata = ((AnnotatedBeanDefinition) beanDef).getMetadata();
}
else if (beanDef instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) beanDef).hasBeanClass()) {
Class<?> beanClass = ((AbstractBeanDefinition) beanDef).getBeanClass();
metadata = new StandardAnnotationMetadata(beanClass, true);
}
else {
try {
MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(className);
metadata = metadataReader.getAnnotationMetadata();
}
catch (IOException ex) {
return false;
}
}
// 查找當前注解是否是與@Configuration相關(guān)
// 該方法還會判斷該注解上的注解是否有@Configuration,一直往上尋找
// 因為有的注解為復合注解
if (isFullConfigurationCandidate(metadata)) {
beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);
}
// 查找當前注解上是否有ComponentScan弧圆、Component赋兵、Import、ImportResource注解
//如果沒有則查找Bean注解搔预,同上霹期,一直往上查找
else if (isLiteConfigurationCandidate(metadata)) {
beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
}
else {
return false;
}
return true;
}
從這里可以猜測,我們使用如下的代碼拯田,程序應該也可以跑起來
//@SpringBootApplication
//@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
@ComponentScan.Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@ComponentScan.Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
@Component
public class SpringBootDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootDemoApplication.class, args);
}
}
上面說了@SpringBootApplication
等價于@SpringBootConfiguration
历造,@EnableAutoConfiguration
,@ComponentScan
注解船庇,而這時吭产,將@SpringBootConfiguration
替換成@Component
之后,checkConfigurationClassCandidate
方法仍然返回true鸭轮,所以程序應該也可以跑起來臣淤。
試著跑了一下,代碼沒問題窃爷,Bean1仍然被初始化邑蒋,而把MyConfiguration類上的注解換成@Component
也是正常沒有問題姓蜂,但是其實兩者還是有差別的,但是不在這篇文章的討論范圍寺董,故不詳細分析覆糟。
4.2 注解解析
解析工作交由ConfigurationClassParser
處理
public void parse(Set<BeanDefinitionHolder> configCandidates) {
this.deferredImportSelectors = new LinkedList<>();
for (BeanDefinitionHolder holder : configCandidates) {
BeanDefinition bd = holder.getBeanDefinition();
try {
if (bd instanceof AnnotatedBeanDefinition) {
parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
}
else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
}
else {
parse(bd.getBeanClassName(), holder.getBeanName());
}
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
}
}
processDeferredImportSelectors();
}
上面說了刻剥,這個時候configCandidates只有一個元素遮咖,即SpringBootDemoApplication,他屬于AnnotatedBeanDefinition
造虏,會走到第一個分支御吞,其實無論哪個分支,最后都會走到如下方法
protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
//....
SourceClass sourceClass = asSourceClass(configClass);
do {
sourceClass = doProcessConfigurationClass(configClass, sourceClass);
}
while (sourceClass != null);
this.configurationClasses.put(configClass, configClass);
}
protected final void parse(@Nullable String className, String beanName) throws IOException {
Assert.notNull(className, "No bean class name for configuration class bean definition");
MetadataReader reader = this.metadataReaderFactory.getMetadataReader(className);
processConfigurationClass(new ConfigurationClass(reader, beanName));
}
protected final void parse(Class<?> clazz, String beanName) throws IOException {
processConfigurationClass(new ConfigurationClass(clazz, beanName));
}
protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {
processConfigurationClass(new ConfigurationClass(metadata, beanName));
}
參數(shù)為ConfigurationClass漓藕,即配置有@Configuration
注解的類對應的ConfigurationClass對象陶珠。
最后將邏輯交由doProcessConfigurationClass
處理,該方法會通過配置有ConfigurationClass對象去獲取額外引入的類(也可能沒有引入)享钞。
最后該方法會又會返回SourceClass
對象揍诽,直到返回的對象為空才結(jié)束解析。
這里是為了解決有父類的情況栗竖,假設(shè)SpringBootDemoApplication有父類暑脆,那么這里返回的SourceClass
為其父類,接著進行解析
解析完成后狐肢,將ConfigurationClass對象放到Map中添吗,表示需要加載到容器中的ConfigurationClass對象集合,后續(xù)會獲取該Map的元素加載到容器中
protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
throws IOException {
// 內(nèi)部類的處理
processMemberClasses(configClass, sourceClass);
// @PropertySource注解處理
//....
// @ComponentScan注解處理
Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
if (!componentScans.isEmpty() &&
!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
//ComponentScan屬性可能有多個份名,因為可能配置了ComponentScans注解
for (AnnotationAttributes componentScan : componentScans) {
// 通過配置的@ComponentScan注解的信息進行包掃描
// 掃描后的類型將注冊成BeanDefinitionHolder并返回
Set<BeanDefinitionHolder> scannedBeanDefinitions =
this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
// 掃描出來的類碟联,又調(diào)用了parse,進行遞歸處理
for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
if (ConfigurationClassUtils.checkConfigurationClassCandidate(
holder.getBeanDefinition(), this.metadataReaderFactory)) {
parse(holder.getBeanDefinition().getBeanClassName(), holder.getBeanName());
}
}
}
}
// 處理@Import注解
//....
// @ImportResource注解處理
//....
// @Bean注解處理
Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
for (MethodMetadata methodMetadata : beanMethods) {
configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
}
// 父類處理
if (sourceClass.getMetadata().hasSuperClass()) {
String superclass = sourceClass.getMetadata().getSuperClassName();
if (superclass != null && !superclass.startsWith("java") &&
!this.knownSuperclasses.containsKey(superclass)) {
this.knownSuperclasses.put(superclass, configClass);
// 返回父類僵腺,讓程序繼續(xù)處理父類的注解
return sourceClass.getSuperClass();
}
}
// 如果沒有父類鲤孵,那么返回null表示不需要繼續(xù)處理
return null;
}
4.2.1 成員類處理
private void processMemberClasses(ConfigurationClass configClass, SourceClass sourceClass) throws IOException {
//獲取成員類的SourceClass對象
Collection<SourceClass> memberClasses = sourceClass.getMemberClasses();
if (!memberClasses.isEmpty()) {
List<SourceClass> candidates = new ArrayList<>(memberClasses.size());
//獲取與@Configuration有關(guān)的
for (SourceClass memberClass : memberClasses) {
if (ConfigurationClassUtils.isConfigurationCandidate(memberClass.getMetadata()) &&
!memberClass.getMetadata().getClassName().equals(configClass.getMetadata().getClassName())) {
candidates.add(memberClass);
}
}
OrderComparator.sort(candidates);
for (SourceClass candidate : candidates) {
//....
processConfigurationClass(candidate.asConfigClass(configClass));
//....
}
}
}
代碼邏輯比較簡單,核心的邏輯還是processConfigurationClass
辰如,該方法只是找到和@Conguration
注解有關(guān)的類
4.2.2 @ComponentScan注解處理
首先會調(diào)用AnnotationConfigUtils.attributesForRepeatable
方法獲取@ComponentScan
和@ComponentScans
注解信息(該信息在@SpringBootApplication注解上裤纹,間接獲取到),當獲取到ComponentScan屬性后丧没,會調(diào)用ComponentScanAnnotationParser#parse
方法進行查找鹰椒,主要看下大概的邏輯
public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) {
// ....
// 獲取basePackages屬性,即進行包掃描的根路徑
Set<String> basePackages = new LinkedHashSet<>();
String[] basePackagesArray = componentScan.getStringArray("basePackages");
for (String pkg : basePackagesArray) {
String[] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg),
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
basePackages.addAll(Arrays.asList(tokenized));
}
//獲取basePackageClasses屬性呕童,以該類所在的包作為掃描路徑
//ClassUtils.getPackageName為獲取當前類所在的包路徑
for (Class<?> clazz : componentScan.getClassArray("basePackageClasses")) {
basePackages.add(ClassUtils.getPackageName(clazz));
}
//如果上面兩個屬性都沒配置漆际,則以參數(shù)declaringClass所在的包作為掃描路徑
if (basePackages.isEmpty()) {
basePackages.add(ClassUtils.getPackageName(declaringClass));
}
//....
//開始掃描并注冊
return scanner.doScan(StringUtils.toStringArray(basePackages));
}
這里只列出了包掃描相關(guān)的代碼,可以看到夺饲,獲取包路徑有三種形式奸汇,
- 在@ComponentScan的
basePackages
屬性配置包路徑 - 在@ComponentScan的
basePackageClasses
屬性配置類信息施符,掃描路徑取自該類所在包的路徑 - 12都沒填寫,那么取自聲明該注解的類所在包作為掃描路徑
從這里可以看到SpringBoot即使不配置掃描路徑也是可以正常跑的(當然也可以在@SpringBootApplication上特意配置指定信息)擂找,這時候會走到第3步戳吝,將啟動類所在的包作為掃描路徑,而這有個前提就是其他需要掃描的包需要放到啟動類所在的包路徑以下贯涎,否則將掃描不到
4.2.3 @Bean注解處理
以Demo為例听哭,通過@SpringBootApplication的ConfigurationClass為入口,掃描得到自定義的配置有@Configuration注解的MyConfiguration
類塘雳,然后又調(diào)用parse方法進行解析陆盘,最后又會調(diào)用到doProcessConfigurationClass
方法,只不過參數(shù)ConfigurationClass對象對應的是MyConfiguration
败明,這時候運行到retrieveBeanMethodMetadata
方法的時候隘马,會獲取MyConfiguration
下配置了@Bean
注解的方法,然后進行處理妻顶。
//獲取所有配置了@Bean注解的方法元數(shù)據(jù)信息
Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
//將其封裝成BeanMethod酸员,并放入到ConfigurationClass對象的集合中待后續(xù)處理
for (MethodMetadata methodMetadata : beanMethods) {
configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
}
這里只是將配置了@Bean方法的信息收集起來,并沒有做特殊處理
4.3 將ConfigurationClass信息加載到容器中
回到ConfigurationClassPostProcessor#processConfigBeanDefinitions
方法讳嘱,當調(diào)用完parse方法之后幔嗦,能得到一批ConfigurationClass集合,但是這時候只是獲取到呢燥,而容器中還沒有對應的注冊信息崭添,那么接下來就是對這批集合進行注冊處理
//上面分析的解析流程,主要是獲取一批ConfigurationClass集合
parser.parse(candidates);
//解析后得到的一批集合
Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
//加載到容器中
this.reader.loadBeanDefinitions(configClasses);
loadBeanDefinitions
方法會調(diào)用到loadBeanDefinitionsForConfigurationClass
方法
private void loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass,
TrackedConditionEvaluator trackedConditionEvaluator) {
//....
//與@Import注解相關(guān)叛氨,后續(xù)文章分析
if (configClass.isImported()) {
registerBeanDefinitionForImportedConfigurationClass(configClass);
}
// 對@Bean注解的到的BeanMethod進行處理
for (BeanMethod beanMethod : configClass.getBeanMethods()) {
loadBeanDefinitionsForBeanMethod(beanMethod);
}
//與@Import注解相關(guān)呼渣,后續(xù)文章分析
loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
}
主要看下loadBeanDefinitionsForBeanMethod方法,其他的和@Import注解有關(guān)寞埠,暫不分析
private void loadBeanDefinitionsForBeanMethod(BeanMethod beanMethod) {
ConfigurationClass configClass = beanMethod.getConfigurationClass();
MethodMetadata metadata = beanMethod.getMetadata();
String methodName = metadata.getMethodName();
//....
//獲取@Bean注解的元數(shù)據(jù)信息
AnnotationAttributes bean = AnnotationConfigUtils.attributesFor(metadata, Bean.class);
Assert.state(bean != null, "No @Bean annotation attributes");
//....
ConfigurationClassBeanDefinition beanDef = new ConfigurationClassBeanDefinition(configClass, metadata);
beanDef.setResource(configClass.getResource());
beanDef.setSource(this.sourceExtractor.extractSource(metadata, configClass.getResource()));
//設(shè)置工廠方法
if (metadata.isStatic()) {
beanDef.setBeanClassName(configClass.getMetadata().getClassName());
beanDef.setFactoryMethodName(methodName);
}
else {
beanDef.setFactoryBeanName(configClass.getBeanName());
beanDef.setUniqueFactoryMethodName(methodName);
}
//....
this.registry.registerBeanDefinition(beanName, beanDefToRegister);
}
上面只列出了核心代碼屁置,主要是構(gòu)造了BeanDefinition,然后注冊進容器仁连,而BeanDefinition的一些屬性則是由注解中獲取蓝角,這部分代碼省略。
另外饭冬,可以看到@Bean的方式構(gòu)造的BeanDefinition的時候使鹅,與普通的不同,這種方式是會設(shè)置工廠方法去初始化昌抠,也就是說患朱,MyConfiguration類下的bean1方法被Spring當成一個工廠方法,也就是說這種方式與下列的初始化方式原理類似:
<bean id="myConfiguration"
class="com.example.springboot.springbootdemo.bean.MyConfiguration"/>
<bean id="bean1" factory-bean="myConfiguration" factory-method="bean1">
</bean>
如果demo中的bean1方法加上static修飾炊苫,就類似xml中配置成靜態(tài)工廠模式
5 總結(jié)
上門介紹了三個注解相關(guān)的處理流程裁厅,流程中涉及的其他注解也會引入更多的ConfigurationClass冰沙,但是涉及篇幅較長,后續(xù)會有其他文章再進行分析执虹。
- 處理的入口為BeanFactoryPostProcessor類的實現(xiàn)拓挥,即ConfigurationClassPostProcessor
- 通過配置了@SpringBootApplication的啟動類為入口,進行處理
- 先獲取所有與@Configuration有關(guān)的類信息袋励,包括@Bean注解的方法信息侥啤,然后再將其轉(zhuǎn)換成BeanDefinition注冊到容器中
- 獲取到一個與@Configuration有關(guān)的類的時候,會獲取該類上的注解(例如@ComponentScan插龄、@Import)愿棋,以此引入更多的ConfigurationClass科展,這里涉及遞歸處理
- 有@Bean注解的方法在解析的時候作為ConfigurationClass的一個屬性蓝厌,最后還是會轉(zhuǎn)換成BeanDefinition進行處理徽千, 而實例化的時候會作為一個工廠方法進行Bean的創(chuàng)建