SpringBoot自动配置原理(超详细)

0. 简介

学习SpringBoot自动配置原理的时候,学得很迷糊,于是自己搜索资料整理思路。本文为作者一天的成果,搜索查看了大量的资料(建议有的时候还是要自己打打断点看看源码比较好),发现网上大部分都讲得很没用,或者讲得很浅。源代码有一些我也无法理解,但是通过打断点获知其方法调用的返回结果渐渐搞清楚方法的作用,整理如下。
参开资料如下:
一文搞懂🔥SpringBoot自动配置原理
Spring Boot面试杀手锏————自动配置原理
@Import注解的作用

1. SprintBoot的自动配置原理:

SpringBoot启动类的注解@SpringBootApplication中,起到关键作用的注解是@EnableAutoConfiguration。而@EnableAutoConfiguration也是一个复合注解,它包含了@AutoConfigurationPackage@Import({AutoConfigurationImportSelector.class})

  • @AutoConfigurationPackage

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

    @Import 为spring的注解,用来导入配置类或者一些需要前置加载的类,通过进一步点击查看源代码,可以看到Registrar.class实现了ImportBeanDefinitionRegistrar接口,通过@Import 导入该接口的实现类会执行重写的registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry)方法。

    static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
        Registrar() {
        }
    
        public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
            AutoConfigurationPackages.register(registry, (String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0]));
    		//在这里打断点观察
        }
    
        public Set<Object> determineImports(AnnotationMetadata metadata) {
            return Collections.singleton(new AutoConfigurationPackages.PackageImports(metadata));
        }
    }
    

    通过断点测试,我们可以发现(String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0])的结果是当前启动类所在的包名。而AutoConfigurationPackages.register(BeanDefinitionRegistry registry, String... packageNames)方法的作用是将启动类所在包名/路径名加入容器中,一个专门记录packageNames的集合里。容器会扫描该集合下的路径名,从而去导入相应的组件。

    即,@AutoConfigurationPackage 就是将主配置类(@SpringBootApplication 标注的类)所在的包下面所有的组件都扫描注冊到 spring 容器中。

  • @Import({AutoConfigurationImportSelector.class}):

    @Import 注解导入的该类,实现了DeferredImportSelector接口,而DeferredImportSelector又实现了ImportSelector接口,因此该类实现了ImportSelector接口。通过@Import导入的该接口的实现类会执行重写的selectImports(AnnotationMetadata annotationMetadata)方法,该方法的返回值是String[]类型,即需要自动配置/导入Spring容器中的类的全限定名。

    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        } else {
            AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
            return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
        }
    }
    

    NO_IMPORTSnew String[0],因此我们关注else部分,而else部分只有两条语句,重点在第一条语句对于getAutoConfigurationEntry(AnnotationMetadata annotationMetadata)的调用。

    protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {    
    	if (!this.isEnabled(annotationMetadata)) {
            return EMPTY_ENTRY;
        } else {
            AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
            
            // 获取所有jar包下的 META-INF/spring.factories 文件定义中,对应的
            // EnableAutoConfiguration 部分作为候选配置
            List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
    		
            // 去掉重复的配置
            configurations = this.removeDuplicates(configurations);
            // 排除部分元注解信息中需要排除的配置,这里是空的
            Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
            this.checkExcludedClasses(configurations, exclusions);
            configurations.removeAll(exclusions);
            
            // 这里的作用是,过滤掉一些不应该生效的自动配置
            // 部分自动配置类会添加注解:@OnXXXCondition,表示满足xx条件下才可以加入自动配置
            configurations = this.getConfigurationClassFilter().filter(configurations);
            this.fireAutoConfigurationImportEvents(configurations, exclusions);
            return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
        }
    

    这里点进去getCandidateConfigurations(annotationMetadata, annotationAttributes)方法,可以看到方法体中调用另外一个方法loadFactoryNames(factoryType, classLoader),其中factoryType的值为EnableAutoConfigurationclassLoader的值为AppClassLoader(打断点可以观察到)。

    在这里插入图片描述

    loadFactoryNames(factoryType, classLoader)最终返回另外一个方法调用的筛选结果,即loadSpringFactories(classLoader),点进去该方法的源码,在该位置打断点进行观察:

    在这里插入图片描述

    可以看到返回值result的结果是一个key-value保存的Map,展开如下。其中就有我们需要并且由loadFactoryNames(factoryType, classLoader)方法返回的EnableAutoConfiguration部分。loadSpringFactories(classLoader)遍历了所有jar包路径下的META-INF/spring.factories文件,这些文件给Spring中的容器组件进行了相关的设置,并汇总成为一个Map集合,最后返回。

    在这里插入图片描述

    从内往外推的顺序如下:

    • loadSpringFactories()方法加载所有jar包下的META-INF/spring.factories文件中的配置,并将其整理为Map,传递出去;

    • loadFactoryNames()接收到loadSpringFactories()的调用结果,挑选出key为EnableAutoConfiguration的部分,即需要自动配置的部分,将其返回;

    • getAutoConfigurationEntry()获得META-INF/spring.factories需要自动配置的部分,在根据@OnXXXCondition注解筛选排除部分不生效的自动配置,将其返回给selectImports()方法;

    • 点进去Map中的EnableAutoConfiguration,可以看到如下全为"xxxxAutoConfiguration"格式的字符串,这就是selectImports()方法返回的需要添加到Spring容器中管理的组件的全限定类名,作用类似于使用@Import引入了@Configuration注解的类,通过这种方式实现自动配置。

      在这里插入图片描述

2. 为什么有的自动配置没有生效/为什么有的配置被过滤掉:

在加载自动配置类的时候,并不是将spring.factories的配置全部加载进来,而是通过@Conditional等注解的判断进行动态加载。@Conditional其实是spring底层注解,意思就是根据不同的条件,来进行自己不同的条件判断,如果满足指定的条件,那么配置类里边的配置才会生效。

常见的Conditional注解:

  • @ConditionalOnClass : classpath中存在该类时起效

  • @ConditionalOnMissingClass : classpath中不存在该类时起效

  • @ConditionalOnBean: DI容器中存在该类型Bean时起效

  • @ConditionalOnMissingBean : DI容器中不存在该类型Bean时起效

  • @ConditionalOnSingleCandidate : DI容器中该类型Bean只有一个或@Primary的只有一个时起效

  • @ConditionalOnExpression : SpEL表达式结果为true时

  • @ConditionalOnProperty : 参数设置或者值一致时起效

  • @ConditionalOnResource : 指定的文件存在时起效

  • @ConditionalOnJndi : 指定的JNDI存在时起效

  • @ConditionalOnJava : 指定的Java版本存在时起效

  • @ConditionalOnWebApplication : Web应用环境下起效

  • @ConditionalOnNotWebApplication : 非Web应用环境下起效

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值