天天看点

手把手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字、超详细)

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