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;
    }

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

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

小結

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

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

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

@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);
    }
}   

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

調試輸出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());
        ...
    }

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

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

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

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業 缺乏曝光? 下一步”網站設計“幫您第一時間規劃公司的門面形象

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

※試算大陸海運運費!