【深入淺出Spring原理及實戰】「原始碼除錯分析」結合DataSourceRegister深入分析ImportBeanDefinitionRegistrar的原始碼運作流程

洛神灬殤發表於2023-01-16

每日一句

人的一生中不可能會一帆風順,總會遇到一些挫折,當你對生活失去了信心的時候,仔細的看一看、好好回想一下你所遇到的最美好的事情吧,那會讓你感覺到生活的美好


注入案例程式碼

如何透過實現SpringBoot框架帶有的ImportBeanDefinitionRegistrar註冊器,注入我們想要註冊的bean物件例項。只需要採用@Import的註解進行注入對應的一類相關的bean物件。

@Import({DataSourceRegister.class,A.class})
@SpringBootApplication
@ComponentScan("com.libo")
public class LiboApplication {
    public static void main(String[] args) {
        SpringApplication sa = new SpringApplication(LiboApplication.class);
        sa.run(args);
    }
}

DataSourceRegister的開發實現

  • 在springboot啟動的時候,loader模組會根據“清單檔案”載入該Application類,並反射呼叫psvm入口函式main,@Import註解也可以匯入一個常規類,並且建立注入很多物件例項

  • DataSourceRegister類是用來進行初始化資料來源和並提供了執行動態切換資料來源的工具類。

DataSourceRegister注入主資料來源和從資料來源

這裡DataSourceRegister繼承的EnvironmentAware介面,沒有真正意義上去用它的用途,本身可以透過這個setEnvironment方法,進行注入Environment物件,從而可以讀取其他的配置資訊,目前主要用作一個hook方法。

讀取對應的環境變數

public final void setEnvironment(Environment environment) {
        DruidEntity druidEntity = FileUtil.
			readYmlByClassPath("db_info", DruidEntity.class);
        defaultTargetDataSource =
			DataSourceUtil.createMainDataSource(druidEntity);
    }

主要用於讀取Druid的資料來源模型資訊。進行建立對應的資料來源物件defaultTargetDataSource

注入Bean到Spring容器中

registerBeanDefinitions註冊BeanDefinition物件模型

    public final void registerBeanDefinitions(AnnotationMetadata  annotationMetadata, BeanDefinitionRegistry  beanDefinitionRegistry) {
        // 0.將主資料來源新增到資料來源集合中
        DataSourceSet.putTargetDataSourcesMap(MAINDATASOURCE,defaultTargetDataSource);
        //1.建立DataSourceBean
        GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
        beanDefinition.setBeanClass(DataSource.class);
        beanDefinition.setSynthetic(true);
        MutablePropertyValues mpv = beanDefinition.getPropertyValues();
        //spring名稱約定為defaultTargetDataSource和targetDataSources
        mpv.addPropertyValue("defaultTargetDataSource",defaultTargetDataSource);
        mpv.addPropertyValue("targetDataSources",DataSourceSet.getTargetDataSourcesMap());
        beanDefinitionRegistry.registerBeanDefinition("dataSource", beanDefinition);
    }

完整的DataSourceRegister的案例

public class DataSourceRegister<T> implements EnvironmentAware, ImportBeanDefinitionRegistrar {

    private javax.sql.DataSource defaultTargetDataSource;

    static final String MAINDATASOURCE = "mainDataSource";

    public final void setEnvironment(Environment environment) {
        DruidEntity druidEntity = FileUtil.
			readYmlByClassPath("db_info", DruidEntity.class);
        defaultTargetDataSource =
			DataSourceUtil.createMainDataSource(druidEntity);
    }

    public final void registerBeanDefinitions(AnnotationMetadata 
											  annotationMetadata, BeanDefinitionRegistry 
											  beanDefinitionRegistry) {
        // 0.將主資料來源新增到資料來源集合中
        DataSourceSet.putTargetDataSourcesMap(MAINDATASOURCE,
		defaultTargetDataSource);
        //1.建立DataSourceBean
        GenericBeanDefinition beanDefinition = new
			GenericBeanDefinition();
        beanDefinition.setBeanClass(DataSource.class);
        beanDefinition.setSynthetic(true);
        MutablePropertyValues mpv = beanDefinition.getPropertyValues();
        //spring名稱約定為defaultTargetDataSource和targetDataSources
        mpv.addPropertyValue("defaultTargetDataSource",
		defaultTargetDataSource);
        mpv.addPropertyValue("targetDataSources",
		DataSourceSet.getTargetDataSourcesMap());
        beanDefinitionRegistry.registerBeanDefinition("dataSource", beanDefinition);
    }
}

執行流程解讀

  • 動態資料來源註冊器類實現了ImportBeanDefinitionRegistrar介面,沒錯就是這個原因,由於實現了該介面讓該類成為了擁有註冊bean的能力。

  • 原理上也能說得通作為一個Bean的註冊類是沒有必要被註冊為Spring容器的Bean物件

  • 雖然這樣解釋也不為過但我仍然想一探究竟,本來想大概找找spring涉及關鍵類如:ConfigurationClass,ConfigurationClassParser等,接下來我們需要看一下SpringBoot的總體載入流程。

SpringApplication類的run方法

SpringBoot啟動時使用了SpringApplication類的run方法來牽引整個spring的初始化過程,原始碼如下。

public ConfigurableApplicationContext run(String... args) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        ConfigurableApplicationContext context = null;
	   // 錯誤原因分析器
        FailureAnalyzers analyzers = null;
        this.configureHeadlessProperty();
	   // 重點分析:啟動所有的執行的監聽器
        SpringApplicationRunListeners listeners = this.getRunListeners(args);
        listeners.starting();
        try {
			// 解析ApplicationArgument資料
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
			// 透過監聽器和傳遞的引數,實現相關的配置環境物件資訊,預先
			// 進行配置Environment物件,設定全域性環境變數容器
            ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
			// 輸出相關的Banner控制,根據配置以及相關的Environment
            Banner printedBanner = this.printBanner(environment);
			// 建立Spring容器上下文。
            context = this.createApplicationContext();
			//建立和賦值錯誤解析器
            analyzers = new FailureAnalyzers(context);
			// 準備環境上下文進行配置相關的容器的上下文的引數。
            this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
			// 重新整理上下文容器
            this.refreshContext(context);
			// 後置執行上下文操作
            this.afterRefresh(context, applicationArguments);
			// 完成監聽器的後置完成處理操作
            listeners.finished(context, (Throwable)null);
            stopWatch.stop();
            if(this.logStartupInfo) {
                (new StartupInfoLogger(this.mainApplicationClass)).
					logStarted(this.getApplicationLog(), stopWatch);
            }
            return context;
        } catch (Throwable var9) {
            this.handleRunFailure(context, listeners, 
								  (FailureAnalyzers)analyzers, 
								  var9);
            throw new IllegalStateException(var9);
        }
    }

根據上面的原始碼流程可以分析重點的載入過程

  1. 重點分析:啟動所有的執行的監聽器,獲取SpringApplicationRunListeners; 從類路徑下META-INF/spring.factories。回撥所有的獲取SpringApplicationRunListener.starting() 方法。
 SpringApplicationRunListeners listeners = this.getRunListeners(args);
 listeners.starting();
  1. 解析ApplicationArgument資料以及封裝命令列引數,透過program引數的部分進行解析,並且載入到PropertiesSourcePlaceHolder中的環境變數容器內部。
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);

透過上面的監聽器和傳遞的引數,實現相關的配置環境物件資訊,預先進行配置Environment物件,設定全域性環境變數容器

  1. 根據上面的全域性變數容器進行配置以及初始化相關的Environment物件。
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
  1. 建立Spring容器上下文,在createApplicationContext當中,由於我們是web專案,則spring預設給我們建立了一個
context = this.createApplicationContext();

AnnotationConfigEmbeddedWebApplicationContext,當然它也是繼承GenericWebApplicationContext類和GenericApplicationContext類的,那麼他預設會持有一個DefaultListableBeanFactory物件,這個物件可以用來建立Bean。

  1. 準備環境上下文進行配置相關的容器的上下文的引數
this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
  1. 重新整理上下文容器,refreshContext就是在做spring執行後的初始化工作。
this.refreshContext(context);

接著往下走,進入refreshContext中會呼叫一系列的refresh方法,最終進入AbstractApplicationContext中,主要將SpringBoot的容器物件資料和原本基礎的Spring Framework的框架物件進行載入到容器中。

   @Override
    public void refresh() throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {
			// Prepare this context for refreshing.
            prepareRefresh();
            // Tell the subclass to refresh the internal bean factory.
            ConfigurableListableBeanFactory beanFactory =
				obtainFreshBeanFactory();
            // Prepare the bean factory for use in this context.
            prepareBeanFactory(beanFactory);
            try {
                // Allows post-processing of the bean factory in context subclasses.
                postProcessBeanFactory(beanFactory);

                // Invoke factory processors registered as beans in the context.
                invokeBeanFactoryPostProcessors(beanFactory);

                // Register bean processors that intercept bean creation.
                registerBeanPostProcessors(beanFactory);

                // Initialize message source for this context.
                initMessageSource();

                // Initialize event multicaster for this context.
                initApplicationEventMulticaster();

                // Initialize other special beans in specific context subclasses.
                onRefresh();

                // Check for listener beans and register them.
                registerListeners();

                // Instantiate all remaining (non-lazy-init) singletons.
                finishBeanFactoryInitialization(beanFactory);

                // Last step: publish corresponding event.
                finishRefresh();
            }

            catch (BeansException ex) {
                if (logger.isWarnEnabled()) {
                    logger.warn("Exception encountered during context 
								initialization - " +"cancelling refresh attempt: " + ex);
                }

                // Destroy already created singletons to avoid dangling resources.
                destroyBeans();

                // Reset 'active' flag.
                cancelRefresh(ex);

                // Propagate exception to caller.
                throw ex;
            }
            finally {
                // Reset common introspection caches in Spring's core, since we
                // might not ever need metadata for singleton beans anymore...
                resetCommonCaches();
            }
        }
    }

invokeBeanFactoryPostProcessors() 方法就是Bean在註冊前期做的一系列資料收集工作,BeanDefinitionRegistry的容器註冊BeanDefinition之前,呼叫相關的

跟著堆疊繼續深入,會進入到這個方法中,這個方法就是初始化bean前的所有軌跡:

在invokeBeanFactoryPostProcessors方法中繼續跟進一系列方法就會看到在一開始的時候spring會初始化幾個系統固有的Bean

繼續除錯後的關鍵點出現在這個方法中:

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
        List<BeanDefinitionHolder> configCandidates = new ArrayList<BeanDefinitionHolder>();
        String[] candidateNames = registry.getBeanDefinitionNames();
        for (String beanName : candidateNames) {
            BeanDefinition beanDef = registry.getBeanDefinition(beanName);
            if (ConfigurationClassUtils.isFullConfigurationClass(beanDef) ||
                    ConfigurationClassUtils.isLiteConfigurationClass(beanDef)) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Bean definition has already been processed 
								 as a configuration class: " + beanDef);
                }
            }
            else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
                configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
            }
        }
        // Return immediately if no @Configuration classes were found
        if (configCandidates.isEmpty()) {
            return;
        }
        // Sort by previously determined @Order value, if applicable
        Collections.sort(configCandidates, new 
						 Comparator<BeanDefinitionHolder>() {
            @Override
            public int compare(BeanDefinitionHolder bd1, 
							   BeanDefinitionHolder bd2) {
                int i1 = 
					ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
                int i2 = 
					ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
                return (i1 < i2) ? -1 : (i1 > i2) ? 1 : 0;
            }
        });
        // Detect any custom bean name generation strategy supplied through the enclosing application context
        SingletonBeanRegistry sbr = null;
        if (registry instanceof SingletonBeanRegistry) {
            sbr = (SingletonBeanRegistry) registry;
            if (!this.localBeanNameGeneratorSet && sbr.containsSingleton(CONFIGURATION_BEAN_NAME_GENERATOR)) {
                BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(CONFIGURATION_BEAN_NAME_GENERATOR);
                this.componentScanBeanNameGenerator = generator;
                this.importBeanNameGenerator = generator;
            }
        }
        // Parse each @Configuration class
        ConfigurationClassParser parser = new ConfigurationClassParser(
                this.metadataReaderFactory, this.problemReporter, this.environment, this.resourceLoader, this.componentScanBeanNameGenerator, registry);
        Set<BeanDefinitionHolder> candidates = new LinkedHashSet<BeanDefinitionHolder>(configCandidates);
        Set<ConfigurationClass> alreadyParsed = new HashSet<ConfigurationClass>(configCandidates.size());
        do {
            parser.parse(candidates);
            parser.validate();
            Set<ConfigurationClass> configClasses = new LinkedHashSet<ConfigurationClass>(parser.getConfigurationClasses());
            configClasses.removeAll(alreadyParsed);
            // Read the model and create bean definitions based on its content
            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();
            if (registry.getBeanDefinitionCount() > candidateNames.length) {
                String[] newCandidateNames = registry.getBeanDefinitionNames();
                Set<String> oldCandidateNames = new HashSet<String>(Arrays.asList(candidateNames));
                Set<String> alreadyParsedClasses = new HashSet<String>();
                for (ConfigurationClass configurationClass : alreadyParsed) {
                  alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());
                }
                for (String candidateName : newCandidateNames) {
                    if (!oldCandidateNames.contains(candidateName)) {
                        BeanDefinition bd = registry.getBeanDefinition(candidateName);
                        if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&
                                !alreadyParsedClasses.contains(bd.getBeanClassName())) {
                            candidates.add(new BeanDefinitionHolder(bd, candidateName));
                        }
                    }
                }
                candidateNames = newCandidateNames;
            }
        }
        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());
            }
        }

        if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) {
            ((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache();
        }
    }

而透過不斷重複除錯確定獲得註冊Bean的列表應該發生在配置的“剖析階段”,也就是parser.parse(candidates);這個方法的內部,到了這裡基本問題的答案已經要浮出水面了,我也不再貼上無用的程式碼,如果你真的對這個問題比驕傲好奇可以自己跟蹤並練習除錯的原始碼技巧!

當然在ConfigurationClassParser這個類中parse方法也是不少,只要靜下心來逐漸分析,馬上就能準確的找到Override的parse方法。

protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
        if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
            return;
        }

        ConfigurationClass existingClass = this.configurationClasses.get(configClass);
        if (existingClass != null) {
            if (configClass.isImported()) {
                if (existingClass.isImported()) {
                    existingClass.mergeImportedBy(configClass);
                }
                // Otherwise ignore new imported config class; existing non-imported class overrides it.
                return;
            }
            else {
                // Explicit bean definition found, probably replacing an import.
                // Let's remove the old one and go with the new one.
                this.configurationClasses.remove(configClass);
                for (Iterator<ConfigurationClass> it = this.knownSuperclasses.values().iterator(); it.hasNext();) {
                    if (configClass.equals(it.next())) {
                        it.remove();
                    }
                }
            }
        }

        // Recursively process the configuration class and its superclass hierarchy.
        SourceClass sourceClass = asSourceClass(configClass);
        do {
            sourceClass = doProcessConfigurationClass(configClass, sourceClass);//處理定義的配置類
        }
        while (sourceClass != null);

        this.configurationClasses.put(configClass, configClass);
    }
protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
            throws IOException {

        // Recursively process any member (nested) classes first
        processMemberClasses(configClass, sourceClass);

        // Process any @PropertySource annotations
        for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
                sourceClass.getMetadata(), PropertySources.class,
                org.springframework.context.annotation.PropertySource.class)) {
            if (this.environment instanceof ConfigurableEnvironment) {
                processPropertySource(propertySource);
            }
            else {
                logger.warn("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
                        "]. Reason: Environment must implement ConfigurableEnvironment");
            }
        }

        // Process any @ComponentScan annotations        Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
                sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
        if (!componentScans.isEmpty() &&
                !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
            for (AnnotationAttributes componentScan : componentScans) {
                // The config class is annotated with @ComponentScan -> perform the scan immediately
                Set<BeanDefinitionHolder> scannedBeanDefinitions =
                        this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
                // Check the set of scanned definitions for any further config classes and parse recursively if needed
                for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
                    if (ConfigurationClassUtils.checkConfigurationClassCandidate(
                            holder.getBeanDefinition(), this.metadataReaderFactory)) {
                        parse(holder.getBeanDefinition().getBeanClassName(), holder.getBeanName());
                    }
                }
            }
        }

        // Process any @Import annotations
        processImports(configClass, sourceClass, getImports(sourceClass), true);//處理註解匯入的型別

        // Process any @ImportResource annotations
        if (sourceClass.getMetadata().isAnnotated(ImportResource.class.getName())) {
            AnnotationAttributes importResource =
                    AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
            String[] resources = importResource.getStringArray("locations");
            Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
            for (String resource : resources) {
                String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
                configClass.addImportedResource(resolvedResource, readerClass);
            }
        }

        // Process individual @Bean methods
        Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
        for (MethodMetadata methodMetadata : beanMethods) {
            configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
        }

        // Process default methods on interfaces
        processInterfaces(configClass, sourceClass);

        // Process superclass, if any
        if (sourceClass.getMetadata().hasSuperClass()) {
            String superclass = sourceClass.getMetadata().getSuperClassName();
            if (!superclass.startsWith("java") && !this.knownSuperclasses.containsKey(superclass)) {
                this.knownSuperclasses.put(superclass, configClass);
                // Superclass found, return its annotation metadata and recurse
                return sourceClass.getSuperClass();
            }
        }

        // No superclass -> processing is complete
        return null;
    }

以上兩個方法中標紅的就是關鍵點。而且spring的大師們也把註釋寫的十分明顯:”//Process any @Import annotations“,到這裡已經徹底豁然開朗!

spring會先去處理scan,將你程式內部的所有要註冊的Bean全部獲得(自然包括那些configuration),這裡統稱為ConfigurationClass,scan全部整理完畢後才會去處理@Import註解時匯入的類!

我們回到最初的問題 DataSourceRegister和A兩個類為什麼A成為了Bean但DataSourceRegister卻未成為Bean呢?

在processImports方法中,很明顯candidate.isAssignable(ImportBeanDefinitionRegistrar.class)時操作為:configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());而普通的類透過processConfigurationClass(candidate.asConfigClass(configClass));方法,最終會被放在ConfigurationClassParser類的成員變數configurationClasses中,最終被初始化為Bean。

private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
            Collection<SourceClass> importCandidates, boolean checkForCircularImports) throws IOException {

        if (importCandidates.isEmpty()) {
            return;
        }

        if (checkForCircularImports && isChainedImportOnStack(configClass)) {
            this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
        }
        else {
            this.importStack.push(configClass);
            try {
                for (SourceClass candidate : importCandidates) {
                    if (candidate.isAssignable(ImportSelector.class)) {
                        // Candidate class is an ImportSelector -> delegate to it to determine imports
                        Class<?> candidateClass = candidate.loadClass();
                        ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);
                        ParserStrategyUtils.invokeAwareMethods(
                                selector, this.environment, this.resourceLoader, this.registry);
                        if (this.deferredImportSelectors != null && selector instanceof DeferredImportSelector) {
                            this.deferredImportSelectors.add(
                                    new DeferredImportSelectorHolder(configClass, (DeferredImportSelector) selector));
                        }
                        else {
                            String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
                            Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
                            processImports(configClass, currentSourceClass, importSourceClasses, false);
                        }
                    }
                    else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
                        // Candidate class is an ImportBeanDefinitionRegistrar ->
                        // delegate to it to register additional bean definitions
                        Class<?> candidateClass = candidate.loadClass();
                        ImportBeanDefinitionRegistrar registrar =
                                BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class);
                        ParserStrategyUtils.invokeAwareMethods(
                                registrar, this.environment, this.resourceLoader, this.registry);
                        configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
                    }
                    else {
                        // Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
                        // process it as an @Configuration class
                        this.importStack.registerImport(
                                currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
                        processConfigurationClass(candidate.asConfigClass(configClass));
                    }
                }
            }
            catch (BeanDefinitionStoreException ex) {
                throw ex;
            }
            catch (Throwable ex) {
                throw new BeanDefinitionStoreException(
                        "Failed to process import candidates for configuration class [" +
                        configClass.getMetadata().getClassName() + "]", ex);
            }
            finally {
                this.importStack.pop();
            }
        }
    }

後置執行上下文操作

this.afterRefresh(context, applicationArguments);

完成監聽器的後置完成處理操作

listeners.finished(context, (Throwable)null);

至此,總體的mportBeanDefinitionRegistrar的物件注入體系就基本介紹完了

相關文章