手把手debug自动装配源码顺带弄懂了@Import等相关的源码(全文3w字超详细)

Posted 张子行的博客

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了手把手debug自动装配源码顺带弄懂了@Import等相关的源码(全文3w字超详细)相关的知识,希望对你有一定的参考价值。

本文目录

前言

说到SpringBoot我们肯定不陌生,自动化的配置让我们在开发的过程中使用的十分爽,例如传统的mvc项目开启aop需要在xml文件中写上一大坨,而在SpringBoot项目中只需加上@EnableAspectJAutoProxy注解就完事了,还是那句话我们不应该只局限于会用,更应该懂原理,这样日后涉及到高阶的知识、或者我们需要横向扩展的时候将会顺畅很多。

SpringBoot启动的时候都做了一些什么事情?

曾几何时不知读者有没有思考过,为什么我们只要书写如下的一个SpringBoot启动类,项目中所有被@Component、@Configuration…注解的类就会被当做Bean来解析,注入到 IOC容器中去了,why?下图是一个很简单SpringBoot项目的启动类。简简单单一个@SpringBootApplication 注解帮我们搞定了所有的事情

@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容器中。

AutoConfigurationImportSelector <==> DeferredImportSelector

可以看到 AutoConfigurationImportSelector 本质为一个 DeferredImportSelector, DeferredImportSelector实现了ImportSelector 接口,效果和 ImportSelector 差不多都是给 IOC 容器中注入一些 BeanDefinition(注意只是注入BeanDefinition,并没有进行实例化Bean),至于差别下文具体分析。下面来思考俩个核心问题

  1. DeferredImportSelector 怎么知道要注入哪些 类 的BeanDefinition?
  2. DeferredImportSelector 是如何注入这些类 的 BeanDefinition?

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来到 invokeBeanFactoryPostProcessors()方法中,调用各种后置处理器,如果beanFactory是BeanDefinitionRegistry类型,从beanFactory中拿出ConfigurationClassBeanPostProcessor 的一个bean,然后紧接着去调用它(这个后置处理器的意思,顾名思义就是初始化处理和配置有关的类),注意里面这段代码 beanFactory.getBean(ppName,BeanDefinitionRegistryPostProcessor.class)涉及到Spring Ioc的源码,没读过Ioc源码的读者,可以这么简单理解:如果没有Bean就创建一个Bean然后返回,如果不存在Bean那么会先创建一个Bean,然后返回。总之一定会返回对应名字的一个Bean出来
值得一提的是,调用后置处理器的顺序是,先是处理实现了 PriorityOrdered 接口的、其次是处理实现了 Ordered接口的、最后处理什么接口都没有实现的后置处理器。如果beanFactory是ConfigurableListableBeanFactory类型,那么后置处理器执行依然是顺序PriorityOrdered 》Ordered 》other
接着debug进入invokeBeanDefinitionRegistryPostProcessors()来到 processConfigBeanDefinitions(),看看spring是如何解析我们的配置类的,利用我们的解析器 ConfigurationClassParser 去解析图中黄色对应类(SpringBoot项目的启动类 PaymentApplication )的 BeanDefinition 信息,具体的解析过程debug进入 parse()方法

研究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的源码。

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集合中,表示你已经被我解析过了

真正处理解析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方法。
可以看到我项目中所有被@Component、@Configuration 、@RestController、@Import…这些注解修饰的类,都被@ComponentScan注解给扫描进来了,然后逐个解析。

解析启动类上的@Import注解源码探究(processImports)

解析完了@ComponentScan注解,然后来解析我们的Import注解了,debug进入this.processImports()。其实无论是解析什么注解,最终都会执行同一个方法,这里先卖个关子,先不说
先不着急分析this.processImports()的源码,我们先来关注一下,它里面的入参是些什么东西,what fark?this.getImports(sourceClass)?????
其实 this.getImports(sourceClass),就是获取主启动类上面所导入的Import组件,其中不乏开篇(@EnableAutoConfiguration、开启自动装配)说过的 AutoConfigurationImportSelector此类即为自动装配的核心,原来你是在这里进行解析的啊!!!!!!!o my gad
对照我们的启动类来看,是不是瞬间清楚了很多呢
现在开始分析正文,来到 processImports()方法咯,这里的代码和@Import、ImportSelector 、ImportBeanDefinitionRegistrar 、DeferredImportSelector的源码有关系,不着急分析源码,先来大致过一遍源码的大体脉络

private void processImports(ConfigurationClass configClass, ConfigurationClassP

以上是关于手把手debug自动装配源码顺带弄懂了@Import等相关的源码(全文3w字超详细)的主要内容,如果未能解决你的问题,请参考以下文章

深入mybatis源码解读~手把手带你debug分析源码

spring boot 自动装配的实现原理和骚操作,不同版本实现细节调整,debug 到裂开......

Spring初学之annotation自动装配

知其然而知其所以然~线程池深入源码分析-手把手debug源码系列

spring:按照Bean的名称自动装配User

从源码中理解Spring Boot自动装配原理