天天看點

手把手debug自動裝配源碼、順帶弄懂了@Import等相關的源碼(全文3w字、超詳細)

本文目錄

    • 前言
    • SpringBoot啟動的時候都做了一些什麼事情?
    • @SpringBootApplication注解結構
    • @EnableAutoConfiguration、開啟自動裝配
    • @AutoConfigurationPackage(确定掃描元件的範圍)
    • AutoConfigurationImportSelector <==> DeferredImportSelector
    • DeferredImportSelector 源碼開始解讀
    • 研究deferredImport、import源碼入口
    • parse方法是如何解析 BeanDefinition的?(processConfigurationClass方法)
    • 真正處了解析configClass的地方(doProcessConfigurationClass)
    • 解析啟動類上的@ComponentScan注解源碼探究
    • 解析啟動類上的@Import注解源碼探究(processImports)
    • 小結processImports()方法
    • DeferredImportSelector源碼解析
    • DeferredImportSelector 怎麼知道要裝配哪些類的BeanDefinition(答)
    • DeferredImportSelector是如何注入這些類的BeanDefinition?
    • BeanDefinitionMap中的資料從哪來的?(loadBeanDefinitions)
    • 個人閱讀spring源碼後的感受
    • 附頁之importSelector與DeferredImportSelector 的差别?
    • 附頁之Bean執行個體化所需的BeanDefinition從何而來?

前言

說到SpringBoot我們肯定不陌生,自動化的配置讓我們在開發的過程中使用的十分爽,例如傳統的mvc項目開啟aop需要在xml檔案中寫上一大坨,而在SpringBoot項目中隻需加上@EnableAspectJAutoProxy注解就完事了,還是那句話我們不應該隻局限于會用,更應該懂原理,這樣日後涉及到高階的知識、或者我們需要橫向擴充的時候将會順暢很多。

SpringBoot啟動的時候都做了一些什麼事情?

曾幾何時不知讀者有沒有思考過,為什麼我們隻要書寫如下的一個SpringBoot啟動類,項目中所有被@Component、@Configuration…注解的類就會被當做Bean來解析,注入到 IOC容器中去了,why?下圖是一個很簡單SpringBoot項目的啟動類。簡簡單單一個@SpringBootApplication 注解幫我們搞定了所有的事情

手把手debug自動裝配源碼、順帶弄懂了@Import等相關的源碼(全文3w字、超詳細)

@SpringBootApplication注解結構

@EnableAutoConfiguration:開啟自動裝配(不算怎麼算本文的核心内容)

@ComponentScan:解析被@Component注解的标注的類,當做一個Bean注入到IOC容器中 (裡面配置了一些過濾規則,不在本文讨論範圍)

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

    @AliasFor(
        annotation = Configuration.class
    )
    boolean proxyBeanMethods() default true;
}
           

@EnableAutoConfiguration、開啟自動裝配

@AutoConfigurationPackage:自動裝配作用于哪些包(具體的這個自動裝配的範圍是多大,下文分析)

@AutoConfigurationImportSelector.class:導入 AutoConfigurationImportSelector 元件(具體這個元件在哪裡解析的,下文分析!!! 如果讀者着急可以直接跳到本文目錄:解析啟動類上的@Import注解源碼探究)

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    Class<?>[] exclude() default {};

    String[] excludeName() default {};
}
           

@AutoConfigurationPackage(确定掃描元件的範圍)

導入 Registrar的這麼一個元件

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({Registrar.class})
public @interface AutoConfigurationPackage {
}
           

Registrar作用:給容器中注入了一個BeanDefinition對象(封裝了描述bean的各種資訊的一個對象),debug進去看一下

static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
        Registrar() {
        }

        public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
            AutoConfigurationPackages.register(registry, (new AutoConfigurationPackages.PackageImport(metadata)).getPackageName());
        }

        public Set<Object> determineImports(AnnotationMetadata metadata) {
            return Collections.singleton(new AutoConfigurationPackages.PackageImport(metadata));
        }
    }
           

可以看到我們的BeanDefinitionRegistry(BeanDefinition系統資料庫)中會将已經是bean形态的BeanDefinition進行一個記錄,注冊到BeanDefinitionRegistry中。從圖中我們可以看到我們com.zzh.payment 以及下面的子包符合條件的類都被當做 bean 注入到IOC容器中。

手把手debug自動裝配源碼、順帶弄懂了@Import等相關的源碼(全文3w字、超詳細)

AutoConfigurationImportSelector <==> DeferredImportSelector

可以看到 AutoConfigurationImportSelector 本質為一個 DeferredImportSelector, DeferredImportSelector實作了ImportSelector 接口,效果和 ImportSelector 差不多都是給 IOC 容器中注入一些 BeanDefinition(注意隻是注入BeanDefinition,并沒有進行執行個體化Bean),至于差别下文具體分析。下面來思考倆個核心問題

  1. DeferredImportSelector 怎麼知道要注入哪些 類 的BeanDefinition?
  2. DeferredImportSelector 是如何注入這些類 的 BeanDefinition?
    手把手debug自動裝配源碼、順帶弄懂了@Import等相關的源碼(全文3w字、超詳細)

DeferredImportSelector 源碼開始解讀

emmm想了一會如何接着寫下文,如果不先搞懂@import的源碼着實無法将DeferredImportSelector的源碼給說明白,是以下文的源碼部分有點長,并且這部分會牽涉到spring的源碼部分。是以想要了解 @import、 DeferredImportSelector、ImportSelector、ImportBeanDefinitionRegistrar、@ComponentScan 的源碼,debug進入下圖示黃的地方就好了,spring在執行個體化Bean之前會做一些準備工作,調用BeanFactory的後置處理器(這些後置處理器的作用就是,通過解析@Configuration、@Component.、@Bean、@Import…這些注解以及DeferredImport、ImportSelector …這些接口,擷取對應的BeanDefinition資訊,然後将所有的BeanDefinition放入一個叫做beanDefinitionMap的容器中,最後在this.finishBeanFactoryInitialization() 中取出beanDefinitionMap中所有的BeanDefinition進行執行個體化Bean)

手把手debug自動裝配源碼、順帶弄懂了@Import等相關的源碼(全文3w字、超詳細)

debug來到 invokeBeanFactoryPostProcessors()方法中,調用各種後置處理器,如果beanFactory是BeanDefinitionRegistry類型,從beanFactory中拿出ConfigurationClassBeanPostProcessor 的一個bean,然後緊接着去調用它(這個後置處理器的意思,顧名思義就是初始化處理和配置有關的類),注意裡面這段代碼 beanFactory.getBean(ppName,BeanDefinitionRegistryPostProcessor.class)涉及到Spring Ioc的源碼,沒讀過Ioc源碼的讀者,可以這麼簡單了解:如果沒有Bean就建立一個Bean然後傳回,如果不存在Bean那麼會先建立一個Bean,然後傳回。總之一定會傳回對應名字的一個Bean出來

手把手debug自動裝配源碼、順帶弄懂了@Import等相關的源碼(全文3w字、超詳細)

值得一提的是,調用後置處理器的順序是,先是處理實作了 PriorityOrdered 接口的、其次是處理實作了 Ordered接口的、最後處理什麼接口都沒有實作的後置處理器。如果beanFactory是ConfigurableListableBeanFactory類型,那麼後置處理器執行依然是順序PriorityOrdered 》Ordered 》other

手把手debug自動裝配源碼、順帶弄懂了@Import等相關的源碼(全文3w字、超詳細)

接着debug進入invokeBeanDefinitionRegistryPostProcessors()來到 processConfigBeanDefinitions(),看看spring是如何解析我們的配置類的,利用我們的解析器 ConfigurationClassParser 去解析圖中黃色對應類(SpringBoot項目的啟動類 PaymentApplication )的 BeanDefinition 資訊,具體的解析過程debug進入 parse()方法

手把手debug自動裝配源碼、順帶弄懂了@Import等相關的源碼(全文3w字、超詳細)

研究deferredImport、import源碼入口

大體可以分為2種情況來解析 BeanDefinitionHolder:

  1. 解析由注解方式注入的類(被@import、@Bean、@Configtion、@RestController…标注的類 )
  2. 解析由自動裝配注入進來的類(META-INF目錄下spring.factories檔案中的類)
public void parse(Set<BeanDefinitionHolder> configCandidates) {
//擷取配置類
        Iterator var2 = configCandidates.iterator();

        while(var2.hasNext()) {
        //擷取配置類Holder
            BeanDefinitionHolder holder = (BeanDefinitionHolder)var2.next();
            //通過Holder來擷取配置類的BeanDefinition
            BeanDefinition bd = holder.getBeanDefinition();
            try {
            //解析注解方式注入的類
                if (bd instanceof AnnotatedBeanDefinition) {
                //解析配置類、import、compentscan等源碼在裡面有展現
                    this.parse(((AnnotatedBeanDefinition)bd).getMetadata(), holder.getBeanName());
                } else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition)bd).hasBeanClass()) {
                    this.parse(((AbstractBeanDefinition)bd).getBeanClass(), holder.getBeanName());
                } else {
                    this.parse(bd.getBeanClassName(), holder.getBeanName());
                }
            } catch (BeanDefinitionStoreException var6) {
                throw var6;
            } catch (Throwable var7) {
                throw new BeanDefinitionStoreException("Failed to parse configuration class [" + bd.getBeanClassName() + "]", var7);
            }
        }
//解析自動裝配注入進來的類
        this.deferredImportSelectorHandler.process();
    }
           

注釋寫的很詳細了,可以看到SpringBoot的啟動類PaymentApplication的BeanDefinition是 AnnotatedBeanDefinition類型的,接着debug,進入 this.parse(((AnnotatedBeanDefinition)bd).getMetadata(), holder.getBeanName()) ,開始分析@Import的源碼。

手把手debug自動裝配源碼、順帶弄懂了@Import等相關的源碼(全文3w字、超詳細)

parse方法是如何解析 BeanDefinition的?(processConfigurationClass方法)

parse()方法本質為 processConfigurationClass()方法,processConfigurationClass()方法将我們的 BeanDefinition包裝成了一個ConfigurationClass 對象,然後來到 processConfigurationClass 方法中,不着急debug,先來分析一下源碼的大體結構,可以先看一下注釋

protected void processConfigurationClass(ConfigurationClass configClass, Predicate<String> filter) throws IOException {
//判斷configClass是否需要跳過解析
        if (!this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
            ConfigurationClass existingClass = (ConfigurationClass)this.configurationClasses.get(configClass);
            //如果configurationClasses中已經存在configClass
            if (existingClass != null) {
                if (configClass.isImported()) {
                    if (existingClass.isImported()) {
                    //configClass、existingClass都沒有被import的情況下,将已有的configClass,與configurationClasses中的configClass進行合并
                        existingClass.mergeImportedBy(configClass);
                    }

                    return;
                }
//configClass不是被import導入,但是已經存在于configurationClasses,移除目前configClass
                this.configurationClasses.remove(configClass);
                this.knownSuperclasses.values().removeIf(configClass::equals);
            }

            ConfigurationClassParser.SourceClass sourceClass = this.asSourceClass(configClass, filter);

            do {
            //真正處理我們配置類的地方、核心
                sourceClass = this.doProcessConfigurationClass(configClass, sourceClass, filter);
            } while(sourceClass != null);
//将configClass放入 configurationClasses、圈重點
            this.configurationClasses.put(configClass, configClass);
        }
    }
           

順着主脈絡走,發現spring會将我們的主啟動類(PaymentApplication)榨幹,隻要 sourceClass != null,那麼将會一直循環執行this.doProcessConfigurationClass(configClass, sourceClass, filter),最終将我們的configClass(PaymentApplication)存入configurationClasses這個Map集合中,表示你已經被我解析過了

手把手debug自動裝配源碼、順帶弄懂了@Import等相關的源碼(全文3w字、超詳細)

真正處了解析configClass的地方(doProcessConfigurationClass)

doProcessConfigurationClass方法源碼

@Nullable
    protected final ConfigurationClassParser.SourceClass doProcessConfigurationClass(ConfigurationClass configClass, ConfigurationClassParser.SourceClass sourceClass, Predicate<String> filter) throws IOException {
        if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
            this.processMemberClasses(configClass, sourceClass, filter);
        }

        Iterator var4 = AnnotationConfigUtils.attributesForRepeatable(sourceClass.getMetadata(), PropertySources.class, PropertySource.class).iterator();

        AnnotationAttributes importResource;
        while(var4.hasNext()) {
            importResource = (AnnotationAttributes)var4.next();
            if (this.environment instanceof ConfigurableEnvironment) {
                this.processPropertySource(importResource);
            } else {
                this.logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() + "]. Reason: Environment must implement ConfigurableEnvironment");
            }
        }

        Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
        if (!componentScans.isEmpty() && !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
            Iterator var14 = componentScans.iterator();

            while(var14.hasNext()) {
                AnnotationAttributes componentScan = (AnnotationAttributes)var14.next();
                Set<BeanDefinitionHolder> scannedBeanDefinitions = this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
                Iterator var8 = scannedBeanDefinitions.iterator();

                while(var8.hasNext()) {
                    BeanDefinitionHolder holder = (BeanDefinitionHolder)var8.next();
                    BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
                    if (bdCand == null) {
                        bdCand = holder.getBeanDefinition();
                    }

                    if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
                        this.parse(bdCand.getBeanClassName(), holder.getBeanName());
                    }
                }
            }
        }

        this.processImports(configClass, sourceClass, this.getImports(sourceClass), filter, true);
        importResource = AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
        if (importResource != null) {
            String[] resources = importResource.getStringArray("locations");
            Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
            String[] var20 = resources;
            int var22 = resources.length;

            for(int var23 = 0; var23 < var22; ++var23) {
                String resource = var20[var23];
                String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
                configClass.addImportedResource(resolvedResource, readerClass);
            }
        }

        Set<MethodMetadata> beanMethods = this.retrieveBeanMethodMetadata(sourceClass);
        Iterator var18 = beanMethods.iterator();

        while(var18.hasNext()) {
            MethodMetadata methodMetadata = (MethodMetadata)var18.next();
            configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
        }

        this.processInterfaces(configClass, sourceClass);
        if (sourceClass.getMetadata().hasSuperClass()) {
            String superclass = sourceClass.getMetadata().getSuperClassName();
            if (superclass != null && !superclass.startsWith("java") && !this.knownSuperclasses.containsKey(superclass)) {
                this.knownSuperclasses.put(superclass, configClass);
                return sourceClass.getSuperClass();
            }
        }

        return null;
    }
           

解析啟動類上的@ComponentScan注解源碼探究

在解析我們的主啟動類之前,會先對SpringBoot項目啟動類上的@ComponentScan注解進行解析,根據解析到被@Component标注的類的的BeanDefinition,重複執行上文分析過的Parse方法。

手把手debug自動裝配源碼、順帶弄懂了@Import等相關的源碼(全文3w字、超詳細)

可以看到我項目中所有被@Component、@Configuration 、@RestController、@Import…這些注解修飾的類,都被@ComponentScan注解給掃描進來了,然後逐個解析。

手把手debug自動裝配源碼、順帶弄懂了@Import等相關的源碼(全文3w字、超詳細)

解析啟動類上的@Import注解源碼探究(processImports)

解析完了@ComponentScan注解,然後來解析我們的Import注解了,debug進入this.processImports()。其實無論是解析什麼注解,最終都會執行同一個方法,這裡先賣個關子,先不說

手把手debug自動裝配源碼、順帶弄懂了@Import等相關的源碼(全文3w字、超詳細)

先不着急分析this.processImports()的源碼,我們先來關注一下,它裡面的入參是些什麼東西,what fark?this.getImports(sourceClass)?????

手把手debug自動裝配源碼、順帶弄懂了@Import等相關的源碼(全文3w字、超詳細)

其實 this.getImports(sourceClass),就是擷取主啟動類上面所導入的Import元件,其中不乏開篇(@EnableAutoConfiguration、開啟自動裝配)說過的 AutoConfigurationImportSelector 類,此類即為自動裝配的核心,原來你是在這裡進行解析的啊!!!!!!!o my gad

手把手debug自動裝配源碼、順帶弄懂了@Import等相關的源碼(全文3w字、超詳細)

對照我們的啟動類來看,是不是瞬間清楚了很多呢

手把手debug自動裝配源碼、順帶弄懂了@Import等相關的源碼(全文3w字、超詳細)

現在開始分析正文,來到 processImports()方法咯,這裡的代碼和@Import、ImportSelector 、ImportBeanDefinitionRegistrar 、DeferredImportSelector的源碼有關系,不着急分析源碼,先來大緻過一遍源碼的大體脈絡

private void processImports(ConfigurationClass configClass, ConfigurationClassParser.SourceClass currentSourceClass, Collection<ConfigurationClassParser.SourceClass> importCandidates, Predicate<String> exclusionFilter, boolean checkForCircularImports) {
        if (!importCandidates.isEmpty()) {
            if (checkForCircularImports && this.isChainedImportOnStack(configClass)) {
                this.problemReporter.error(new ConfigurationClassParser.CircularImportProblem(configClass, this.importStack));
            } else {
                this.importStack.push(configClass);

                try {
                    Iterator var6 = importCandidates.iterator();

                    while(var6.hasNext()) {
                        ConfigurationClassParser.SourceClass candidate = (ConfigurationClassParser.SourceClass)var6.next();
                        Class candidateClass;
                        if (candidate.isAssignable(ImportSelector.class)) {
                            candidateClass = candidate.loadClass();
                            ImportSelector selector = (ImportSelector)ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class, this.environment, this.resourceLoader, this.registry);
                            Predicate<String> selectorFilter = selector.getExclusionFilter();
                            if (selectorFilter != null) {
                                exclusionFilter = exclusionFilter.or(selectorFilter);
                            }

                            if (selector instanceof DeferredImportSelector) {
                                this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector)selector);
                            } else {
                                String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
                                Collection<ConfigurationClassParser.SourceClass> importSourceClasses = this.asSourceClasses(importClassNames, exclusionFilter);
                                this.processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false);
                            }
                        } else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
                            candidateClass = candidate.loadClass();
                            ImportBeanDefinitionRegistrar registrar = (ImportBeanDefinitionRegistrar)ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class, this.environment, this.resourceLoader, this.registry);
                            configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
                        } else {
                            this.importStack.registerImport(currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
                            this.processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);
                        }
                    }
                } catch (BeanDefinitionStoreException var17) {
                    throw var17;
                } catch (Throwable var18) {
                    throw new BeanDefinitionStoreException("Failed to process import candidates for configuration class [" + configClass.getMetadata().getClassName() + "]", var18);
                } finally {
                    this.importStack.pop();
                }
            }

        }
    }
           

觀察源碼我們可以發現源碼的設計思路為:挨個周遊由主啟動類上注解注冊的元件,如果元件類型為 ImportSelector,擷取 selectImports方法中的導入類名,然後将其封裝成一個SourceClass對象,接着對SourceClass 進行解析、也就是執行processImports(SourceClass)方法,之後就是對 SourceClass進行下圖中的判斷了。

手把手debug自動裝配源碼、順帶弄懂了@Import等相關的源碼(全文3w字、超詳細)

來到ImportSelector分支後,還會繼續判斷元件類型如果是 DeferredImportSelector 類型的,進行對應的操作。

手把手debug自動裝配源碼、順帶弄懂了@Import等相關的源碼(全文3w字、超詳細)

接着我們的debug進入 this.deferredImportSelectorHandler.handle()方法,可以看到我們的 AutoConfigurationImportSelector 此時已經存進 deferredImportSelectors中了,記在小本本上面

public void handle(ConfigurationClass configClass, DeferredImportSelector importSelector) {
            ConfigurationClassParser.DeferredImportSelectorHolder holder = new ConfigurationClassParser.DeferredImportSelectorHolder(configClass, importSelector);
            if (this.deferredImportSelectors == null) {
                ConfigurationClassParser.DeferredImportSelectorGroupingHandler handler = ConfigurationClassParser.this.new DeferredImportSelectorGroupingHandler();
                handler.register(holder);
                handler.processGroupImports();
            } else {
            //将配置類存入 deferredImportSelectors 中
                this.deferredImportSelectors.add(holder);
            }

        }
           
手把手debug自動裝配源碼、順帶弄懂了@Import等相關的源碼(全文3w字、超詳細)

如果是 ImportSelector 類型的元件進行如下操作

手把手debug自動裝配源碼、順帶弄懂了@Import等相關的源碼(全文3w字、超詳細)

如果是 ImportBeanDefinitionRegistrar 類型的進行如下操作,将元件的Class存放到 importBeanDefinitionRegistrars 的一個map中

手把手debug自動裝配源碼、順帶弄懂了@Import等相關的源碼(全文3w字、超詳細)
手把手debug自動裝配源碼、順帶弄懂了@Import等相關的源碼(全文3w字、超詳細)

如果是 普通類型 進行如下操作,再次回到了 processConfigurationClass(),會再次經曆對該類進行@Component、@Import、DeferredImportSelector、ImportSelector、ImportBeanDefinitionRegistrar相關的解析操作,那問題來了。什麼時候終止這個循環呢?

手把手debug自動裝配源碼、順帶弄懂了@Import等相關的源碼(全文3w字、超詳細)

其實對該類進行@Component、@Import、DeferredImportSelector、ImportSelector、ImportBeanDefinitionRegistrar的解析是有一個前提條件的,那就是 sourceClass!=null,但是如果是普通類那麼 sourceClass=null,也就終止循環了,直接将這個普通類型的configClass存入configurationClasses中

手把手debug自動裝配源碼、順帶弄懂了@Import等相關的源碼(全文3w字、超詳細)

小結processImports()方法

processImports方法會對我們的類進行一系列抽絲剝繭的遞歸解析操作,如果發現你是ImportSelector類型的類,那麼會接着解析你對應selectImport()方法中的類,如果selectImport()方法中還存在ImportSelector類型的類,接着遞歸解析,直到selectImport()方法傳回的全是一些普通的類,最後将這些普通類的BeanDefinition,包裝成一個configClass對象存入configurationClasses這個map中去。但是此時自動裝配的類的BeanDefinition并未存入configurationClasses中,而是在this.deferredImportSelectorHandler.process()中,才将自動裝配的類的BeanDefinition存入configurationClasses,具體存放過程下文有解析。

等我們解析完啟動類注冊進來的所有元件之後,最終會return null哦!!!!!return null也就意味着目前的configClass已經解析完畢,可以存入configurationClasses了。(标志着主啟動類以及主啟動類身上的所有元件已經解析完畢,并且對應的configClass分類存進了不同的Map中)

  • @import、importSelector、普通類、類型的 BeanDefinition最終經過包裝,存入名字為configurationClasses 的map中
  • DeferredImportSelector類型的 BeanDefinition 最終經過包裝,會存入 名字為 deferredImportSelectors 的map中
  • ImportBeanDefinitionRegistrar 類型的 BeanDefinition最終經過包裝,會存入為一個名字為 importBeanDefinitionRegistrars 的Map中
手把手debug自動裝配源碼、順帶弄懂了@Import等相關的源碼(全文3w字、超詳細)

DeferredImportSelector源碼解析

言歸正傳,回到我們的parse方法中,終于debug到了這裡,此時由注解方式注入的類的BeanDefinition已經解析完成了,(開始解析自動裝配進來的類)進去看看咯,源碼貼圖如下在此

手把手debug自動裝配源碼、順帶弄懂了@Import等相關的源碼(全文3w字、超詳細)

圖中藍色箭頭還是跳過。。。。。。。本人畫錯了圖,懶不想改了。。。。。在解析注解注入的類的BeanDefinition時,已經将DeferredImportSelector類型的類的存入到deferredImportSelectors中了。是以debug會來到handler.processGroupImports() 中。

手把手debug自動裝配源碼、順帶弄懂了@Import等相關的源碼(全文3w字、超詳細)

其實processGroupImports中做的事情很簡單,根據對應的gruop解析group内含的DeferredImportSelector元件,調用對應的getImports()方法(擷取要自動裝配類的一些定義資訊,封裝在entry對象裡面),然後對每個entry對象再次進行processImport()方法的調用,最終自動裝配的類的BeanDefinition也會存入configurationClasses中。至于processImport()方法上文已經分析過了。現在重點來分析DeferredImportSelector中的getImports()方法。

手把手debug自動裝配源碼、順帶弄懂了@Import等相關的源碼(全文3w字、超詳細)

DeferredImportSelector 怎麼知道要裝配哪些類的BeanDefinition(答)

還記得文章開頭我提出的倆個問題嗎,DeferredImportSelector 怎麼知道要裝配哪些類的BeanDefinition?答案就在下圖中的代碼裡面,别着急一步步開始分析。

手把手debug自動裝配源碼、順帶弄懂了@Import等相關的源碼(全文3w字、超詳細)

debug進入this.group.process()方法中,方法很簡單将需要自動裝配的類,封裝成AutoConfigurationEntry對象存入autoConfigurationEntries中,那問題來了,這個autoConfigurationEntries在哪裡使用呢?getAutoConfigurationEntry()?從哪裡擷取要自動裝配的類?

手把手debug自動裝配源碼、順帶弄懂了@Import等相關的源碼(全文3w字、超詳細)

getAutoConfigurationEntry方法沒啥好看的,看下面的圖檔就好了

手把手debug自動裝配源碼、順帶弄懂了@Import等相關的源碼(全文3w字、超詳細)

擷取候選的要自動裝配進來的類,點進去SpringFactoriesLoader.loadFactoryNames()瞧瞧吧,但是在此注意一下入參吧,入參如下是不是覺着有點熟悉呢?不就是EnableAutoConfiguration 嗎,仔細瞧瞧下面幾張圖相信讀者定會豁然開朗

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
        List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
        return configurations;
    }
           
手把手debug自動裝配源碼、順帶弄懂了@Import等相關的源碼(全文3w字、超詳細)
手把手debug自動裝配源碼、順帶弄懂了@Import等相關的源碼(全文3w字、超詳細)

接下來就是開始從spring.factories檔案讀取EnableAutoConfiguration節點的valus了,這些類會在之後的IOC容器初始化的時候,對其進行執行個體化,注入到IOC容器中

手把手debug自動裝配源碼、順帶弄懂了@Import等相關的源碼(全文3w字、超詳細)

可以看到我們的b類已經被掃描進來了,最終 “com.zzh.payment.testBean.b”這個字元串會被封裝成一個AutoConfigurationEntry對象存入autoConfigurationEntries這個map中,到此回到本小節的這個問題就已經解答了哦。

手把手debug自動裝配源碼、順帶弄懂了@Import等相關的源碼(全文3w字、超詳細)

DeferredImportSelector是如何注入這些類的BeanDefinition?

言歸正傳,我們的第一步已經完成了,開始我們的第二步了。

手把手debug自動裝配源碼、順帶弄懂了@Import等相關的源碼(全文3w字、超詳細)

源碼如下selectImports。也是在AutoConfigurationImportSelector類中,将autoConfigurationEntries中的對象挨個周遊,再次封裝成一個entry對象的疊代器傳回

public Iterable<Entry> selectImports() {
        if (this.autoConfigurationEntries.isEmpty()) {
            return Collections.emptyList();
        } else {
            Set<String> allExclusions = (Set)this.autoConfigurationEntries.stream().map(AutoConfigurationImportSelector.AutoConfigurationEntry::getExclusions).flatMap(Collection::stream).collect(Collectors.toSet());
            Set<String> processedConfigurations = (Set)this.autoConfigurationEntries.stream().map(AutoConfigurationImportSelector.AutoConfigurationEntry::getConfigurations).flatMap(Collection::stream).collect(Collectors.toCollection(LinkedHashSet::new));
            processedConfigurations.removeAll(allExclusions);
            return (Iterable)this.sortAutoConfigurations(processedConfigurations, this.getAutoConfigurationMetadata()).stream().map((importClassName) -> {
                return new Entry((AnnotationMetadata)this.entries.get(importClassName), importClassName);
            }).collect(Collectors.toList());
        }
    }
           

最終對每個enrty對象挨個processImport(),在調用完最終b這個類會被放入configurationClasses中

手把手debug自動裝配源碼、順帶弄懂了@Import等相關的源碼(全文3w字、超詳細)

我這裡用了一個條件斷點,條件為((ClassPathResource)configClass.getResource()).getPath().equals(“com/zzh/payment/testBean/b.class”)

手把手debug自動裝配源碼、順帶弄懂了@Import等相關的源碼(全文3w字、超詳細)

等到所有需要自動裝配的類都解析完畢後,來到 this.reader.loadBeanDefinitions(configClasses) 方法,開始将所有需要注入到IOC容器的BeanDefinition,放入BeanDefinitionMap中

手把手debug自動裝配源碼、順帶弄懂了@Import等相關的源碼(全文3w字、超詳細)

BeanDefinitionMap中的資料從哪來的?(loadBeanDefinitions)

挨個周遊configurationClasses中的configClass,然後進行加載

public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {
        ConfigurationClassBeanDefinitionReader.TrackedConditionEvaluator trackedConditionEvaluator = new ConfigurationClassBeanDefinitionReader.TrackedConditionEvaluator();
        Iterator var3 = configurationModel.iterator();

        while(var3.hasNext()) {
            ConfigurationClass configClass = (ConfigurationClass)var3.next();
            this.loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
        }

    }
           

打斷點進入loadBeanDefinitions()方法後,中間不是很重要的過程就先省略了,最終會來到下圖這,當然我這裡使用了一個條件斷點 ((ClassPathResource) configClass.getResource()).getPath().equals(“com/zzh/payment/testBean/b.class”),才定位到的

手把手debug自動裝配源碼、順帶弄懂了@Import等相關的源碼(全文3w字、超詳細)

最終将類的各種資訊都進行整理一下,設定bean的作用域、bean的名字啊等等、最終進行注冊BeanDefinition

private void registerBeanDefinitionForImportedConfigurationClass(ConfigurationClass configClass) {
        AnnotationMetadata metadata = configClass.getMetadata();
        AnnotatedGenericBeanDefinition configBeanDef = new AnnotatedGenericBeanDefinition(metadata);
        ScopeMetadata scopeMetadata = scopeMetadataResolver.resolveScopeMetadata(configBeanDef);
        configBeanDef.setScope(scopeMetadata.getScopeName());
        String configBeanName = this.importBeanNameGenerator.generateBeanName(configBeanDef, this.registry);
        AnnotationConfigUtils.processCommonDefinitionAnnotations(configBeanDef, metadata);
        BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(configBeanDef, configBeanName);
        definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
        //進行注冊BeanDefinition
        this.registry.registerBeanDefinition(definitionHolder.getBeanName(), definitionHolder.getBeanDefinition());
        configClass.setBeanName(configBeanName);
        if (logger.isTraceEnabled()) {
            logger.trace("Registered bean definition for imported class '" + configBeanName + "'");
        }

    }
           

然後我們的b類就被注冊成功了。到此,自動裝配的整套流程就已經完成了

手把手debug自動裝配源碼、順帶弄懂了@Import等相關的源碼(全文3w字、超詳細)

個人閱讀spring源碼後的感受

  1. 自動裝配對标springboot項目與xml的,其實更像是扮演xml的角色存在,因為它不是基于注解掃描加載的,他需要依賴檔案。
  2. 開啟自動裝配從嚴格意義上來說,并不是開始IOC對bean的注入,開啟自動裝配僅僅是将所需注入IOC容器的類的BeanDefinition注入到BeanDefinitionMap中,真正的從類到bean的過程,還需要走完Bean生命周期中的執行個體化那一環
  3. 自動裝配個人感覺有點像懶加載,其實META-INF目錄下的spring.factories檔案,在項目啟動的時候就已經經過掃描解析,并存入本地緩存中去了(一個cache的map),當我們開啟自動裝配的時候,有優先從緩存中擷取對應的資訊
  4. 要想真正讀懂源碼不親自debug一遍,真的是一知半解,本文也隻是粗略的講述了一下springBoot關于注入方面注解的大體解析過程。
  5. 了解自動裝配的原理後,其實我們也就知道,我們引入的maven依賴的jar包的工作原理了,因為在springBoot項目啟動的時候,會自動掃描項目,以及jar包中的所有bean,然後注入到IOC容器中,我們在開發的時候,即使你沒有配置過相關的bean,但是也能有預設得bean可以使用,原因就是jar包中配置的bean被自動裝配到我們的IOC容器中來了哦,其實這也就是starter啟動器的核心之處了

附頁之importSelector與DeferredImportSelector 的差别?

  1. 執行時機的不同:importSelector類型的類的BeanDefinition會先注冊到BeanDefinitionMap中
    手把手debug自動裝配源碼、順帶弄懂了@Import等相關的源碼(全文3w字、超詳細)
  2. DeferredImportSelector 在進行注冊BeanDefinition之前會對這些類的BeanDefinition進行一個排序
    手把手debug自動裝配源碼、順帶弄懂了@Import等相關的源碼(全文3w字、超詳細)
    手把手debug自動裝配源碼、順帶弄懂了@Import等相關的源碼(全文3w字、超詳細)

附頁之Bean執行個體化所需的BeanDefinition從何而來?

結合之前我閱讀spring IOC的源碼經驗,貼幾張圖供大家參考。發現原來@Import注解也不過是為bean執行個體化做準備嘛

手把手debug自動裝配源碼、順帶弄懂了@Import等相關的源碼(全文3w字、超詳細)
手把手debug自動裝配源碼、順帶弄懂了@Import等相關的源碼(全文3w字、超詳細)
手把手debug自動裝配源碼、順帶弄懂了@Import等相關的源碼(全文3w字、超詳細)

分析源碼不易,點個便是給予我最大的動力

手把手debug自動裝配源碼、順帶弄懂了@Import等相關的源碼(全文3w字、超詳細)

撒花---------------------