Spring註解@Configuration的詳細解析

從SpringApplication開始

一般情況下啟動SpringBoot都是新建一個類包含main方法,然後使用SpringApplication.run來啟動程序:


@SpringBootApplication
public class AutoConfigApplication {

public static void main(String[] args){
ConfigurableApplicationContext configurableApplicationContext = SpringApplication.run(AutoConfigApplication.class,args);
}
}

SpringApplication.run接收兩個參數分別為:primarySource、運行參數(args),上面的代碼使用AutoConfigApplication.class作為primarySource。SpringApplication還有一個實例方法也叫run,SpringBoot的大部分啟動都由實例run方法來完成的,其中構造ApplicationContext由createApplicationContext方法完成:

protected ConfigurableApplicationContext createApplicationContext() {
Class> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
switch (this.webApplicationType) {
case SERVLET:
contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
break;
case REACTIVE:
contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
break;
default:
contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
}
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException(
"Unable create a default ApplicationContext, please specify an ApplicationContextClass", ex);
}
}
return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}

createApplicationContext根據this.webApplicationType來構造ApplicationContext,不同的環境都會使用不同的實例,但本文非web環境所有構造的時候會使用AnnotationConfigApplicationContext類。創建AnnotationConfigApplicationContext的時候會調用默認構造方法:

public AnnotationConfigApplicationContext() {
this.reader = new AnnotatedBeanDefinitionReader(this);
this.scanner = new ClassPathBeanDefinitionScanner(this);
}

AnnotationConfigApplicationContext默認構造函數創建兩個對象:

  • reader(AnnotatedBeanDefinitionReader):用於手動註冊bean
  • scanner(ClassPathBeanDefinitionScanner): 用於掃描Component、Repository、Service等註解

AnnotatedBeanDefinitionReader和ClassPathBeanDefinitionScanner會註冊一些註解處理器,註冊的方式都是使用AnnotationConfigUtils的registerAnnotationConfigProcessors方法

public static Set<beandefinitionholder> registerAnnotationConfigProcessors(
BeanDefinitionRegistry registry, @Nullable Object source) {

...

if (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) {
RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class);
def.setSource(source);
beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME));
}
...
return beanDefs;
}/<beandefinitionholder>

最終AnnotationConfigApplicationContext構造方法執行完成後ApplicationContext會有以下BeanDefinition:

Spring註解@Configuration的詳細解析

構造完ApplicationContext後SpringApplicaiton緊接著會加載primarySource,上面提到 過primarySource是在運行的時候傳遞進來的(AutoConfigApplication.class),加載過程中不貼代碼了,只要知道最終ApplicaitonContext中會多一個AutoConfigApplication的BeanDefinition:

Spring註解@Configuration的詳細解析

小結

總的來說SpringApplicaiton主要乾了這些事:

  • 創建AnnotationConfigApplicationContext
  • 加載一些處理註解的後處理器如:ConfigurationClassPostProcessor
  • 將primarySource加載進ApplicationContext

最重要的一點是,現在是有一個AnnotationConfigApplicationContext裡面包含了primarySource(AutoConfigApplication)以及ConfigurationClassPostProcessor。打個斷點在ApplicaitonContext刷新之前打印下context中的bean的名稱,可以確定這樣說沒毛病!

Spring註解@Configuration的詳細解析

@Configuration啥時候被解析?

雖說有了primarySource和ConfigurationClassPostProcessor後處理器,還是需要有個執行的入口。ConfigurationClassPostProcessor是BeanDefinitionRegistryPostProcessor的實現類,BeanDefinitionRegistryPostProcessor會在ApplicationContext的refresh操作時被處理:

public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
...
invokeBeanFactoryPostProcessors(beanFactory);
...
}
}

public static void invokeBeanFactoryPostProcessors(
ConfigurableListableBeanFactory beanFactory, List<beanfactorypostprocessor> beanFactoryPostProcessors) {

...
//找出所有類型為BeanDefinitionRegistryPostProcessor的bean的名稱
String[] postProcessorNames =
beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
for (String ppName : postProcessorNames) {
if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
processedBeans.add(ppName);
}
}
sortPostProcessors(currentRegistryProcessors, beanFactory);
registryProcessors.addAll(currentRegistryProcessors);
//執行BeanDefinitionRegistryPostProcessor
invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
...
}

private static void invokeBeanDefinitionRegistryPostProcessors(
Collection extends BeanDefinitionRegistryPostProcessor> postProcessors, BeanDefinitionRegistry registry) {

for (BeanDefinitionRegistryPostProcessor postProcessor : postProcessors) {
//調用postProcessBeanDefinitionRegistry方法
postProcessor.postProcessBeanDefinitionRegistry(registry);
}
} /<beanfactorypostprocessor>

invokeBeanDefinitionRegistryPostProcessors會調用BeanDefinitionRegistryPostProcessor的postProcessBeanDefinitionRegistry方法,通過斷點調試工具確認下ConfigurationClassPostProcessor有沒有在這一步被處理:

Spring註解@Configuration的詳細解析

調試輸出postProcessors集合裡面有一個了ConfigurationClassPostProcessor元素,說明了ConfigurationClassPostProcessor的執行入口沒有問題。

ConfigurationClassPostProcessor處理器

ConfigurationClassPostProcessor首先會判斷在ApplicationContext中的bean是否被@Configuration註解標記,然後使用ConfigurationClassParser來解析@Configuration,ConfigurationClassPostProcessor的解析@Configuration的大致流程:

  1. 使用ConfigurationClassUtils.checkConfigurationClassCandidate檢查BeanDefinition是否@Configuration註解標記
  2. 對@Configuration進行排序
  3. 使用ConfigurationClassParser解析@Configuration註解的信息
  4. 使用ConfigurationClassBeanDefinitionReader解析BeanDefinition
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
List<beandefinitionholder> configCandidates = new ArrayList<>();

//獲取所有BeanDefinitio名稱
String[] candidateNames = registry.getBeanDefinitionNames();

for (String beanName : candidateNames) {
BeanDefinition beanDef = registry.getBeanDefinition(beanName);
//如果是full、lite則說明已經處理過的類
if (ConfigurationClassUtils.isFullConfigurationClass(beanDef) ||
ConfigurationClassUtils.isLiteConfigurationClass(beanDef)) {
if (logger.isDebugEnabled()) {
logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
}
}
//檢查BeanDefinition是否有@Configuration註解
else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
}
}

//如果沒有找到@Configuration標記的類,則返回不作處理也
if (configCandidates.isEmpty()) {
return;
}

//對@Configuration進行排序
configCandidates.sort((bd1, bd2) -> {
int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
return Integer.compare(i1, i2);
});

...

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 {
//解析@Configuration class
parser.parse(candidates);
parser.validate();

Set<configurationclass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
configClasses.removeAll(alreadyParsed);

//讀取BeanDefinition
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());
...
}/<configurationclass>/<configurationclass>/<beandefinitionholder>/<beandefinitionholder>

最後還是通過調試工具看一下示例中的的啟動類AutoConfigApplication沒有被處理:

Spring註解@Configuration的詳細解析

圖上顯示configCandidates中有一個名稱為autoConfigApplication的BeanDefinition的元素,說明AutoConfigApplication會被當作配置類解析,但是AutoConfigApplication並沒有使用@Configuration註解,為什麼還會被當做配置類呢?其實@Configuration在@SpringBootApplication註解中:

Spring註解@Configuration的詳細解析

紅色背景列出來的就是@Configuration註解,它是@SpringBootConfiguration的元註解。


分享到:


相關文章: