SpringBoot自动配置原理解析

135 阅读5分钟

SpringBoot 版本:2.6.3

@SpringBootApplication 注解的整体结构

为了防止头晕,先来个总览

image.png

从最外头的@SpringBootApplication注解开始逐层分析

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

1、@ComponentScan 和 @SpringBootConfiguration

看到了里面包着@ComponentScan@SpringBootConfiguration,加上一个最重要的@EnableAutoConfiguration

前两个的源码定义一起看看(下面的源码定义省略了其他元注解)

@Configuration // 从这里看出,这个注解是作为一个配置类
public @interface SpringBootConfiguration { 
    
}
​
//====================================================@Repeatable(ComponentScans.class) // @ComponentScan 没什么好说的了,就一个包扫描
public @interface ComponentScan { 
    
}

这两个注解没啥特别之处,一个充当配置类,另一个是包扫描

2、@EnableAutoConfiguration

这个注解的作用,顾名思义:启用 SpringBoot 的自动配置机制,帮助 SpringBoot 应用将所有符合条件的 @Configuration 配置都加载到当前容器,并创建对应配置类的 Bean,并把该 Bean 实体交给 IoC 容器进行管理

注解定义

// 上面的其他元注解就不截取下来了
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration { 
​
}

2.1、第一个核心 @AutoConfigurationPackage

自动配置包?

作用:添加该注解的类所在的 package 作为 自动配置 package 进行管理,它负责保存标了注解的类的所在包路径

注解定义

// 表示对于标注该注解的类的包,使用AutoConfigurationPackages注册
@Import(AutoConfigurationPackages.Registrar.class) //导入一个组件
public @interface AutoConfigurationPackage { 
    // 将指定的一个包下的所有组件导入进来,主启动类所在的包下
}

2.1.1、AutoConfigurationPackages.Registrar

来到了实现自动包机制的这个组件,这个 Registrar 是 AutoConfigurationPackages 类中的一个静态内部类

看看这个组件的定义

static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
        
    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
    }
​
    @Override
    public Set<Object> determineImports(AnnotationMetadata metadata) {
        return Collections.singleton(new PackageImports(metadata));
    }
}

实践是检验真理的唯一标准,来 Debug 看看

RegistrarregisterBeanDefinitions(xxx)方法打上断点,看看啥情况

register.png

2.1.2、分析registerBeanDefinitions()

/**
 * AnnotationMetadata: 这个参数相当于是传入了注解的原信息,
 * 这个注解指的就是 @AutoConfigurationPackage
 * 原信息表示这个注解标注在了哪里,这里显示标注在了主启动类上。
 * 因为这一堆注解都是合成注解,所以都相当于标注在了主启动类上
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
    /**
     * new PackageImports(metadata).getPackageNames():拿到主启动类所在的包路径
     * 然后通过 register() 将组件批量注册进来
     */
    register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
}

image.png

2.2、第二个核心 @Import(AutoConfigurationImportSelector.class)

组件导入的选择器

2.2.1、AutoConfigurationImportSelector 的核心方法

// 组件导入的选择器,由他来规定导入那些组件
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
   if (!isEnabled(annotationMetadata)) {
      return NO_IMPORTS;
   }
   // *** 重点 ***
   AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata); 
   return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
​
//获取所有自动配置的集合
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
   if (!isEnabled(annotationMetadata)) {
      return EMPTY_ENTRY;
   }
   AnnotationAttributes attributes = getAttributes(annotationMetadata);
   //获取所有候选的配置,得到一个集合
   List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes); 
   configurations = removeDuplicates(configurations); //移除、去重、排除操作
   Set<String> exclusions = getExclusions(annotationMetadata, attributes); //移除、去重、排除操作
   checkExcludedClasses(configurations, exclusions); //移除、去重、排除操作
   configurations.removeAll(exclusions); //移除、去重、排除操作
   configurations = getConfigurationClassFilter().filter(configurations); //
   fireAutoConfigurationImportEvents(configurations, exclusions); //
   return new AutoConfigurationEntry(configurations, exclusions); // 操作完毕,封装返回
}

实践是检验真理的唯一标准,来 Debug 看看

image.png

image.png

完成了去重和筛选后,只剩下 28 个元素

image.png

注:因为 SpringBoot 版本不同,size 数值可能有一些差异。我是用的是 2.6.3

2.2.1.1、SpringBoot 是怎么知道要加载137个自动配置类的?

重点就是getAutoConfigurationEntry()方法的getCandidateConfigurations(annotationMetadata, attributes)

跟着源码逐步进去看看

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
   // 利用加载工厂获取这137个类的全类名
   List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
         getBeanClassLoader()); //从这里进去
   Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
         + "are using a custom packaging, make sure that file is correct.");
   return configurations;
}
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
   ClassLoader classLoaderToUse = classLoader;
   if (classLoaderToUse == null) {
      classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
   }
   String factoryTypeName = factoryType.getName();
   //继续从这个 loadSpringFactories() 方法进去 
   return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}

最后就是利用这个 loadSpringFactories() 方法获取所有的组件

至于是从哪里获取? 看看这个方法的源码就知道了!

是从这个方法里面获取这一系列的资源文件

image.png 也就是从 META-INF/spring.factories 这个位置加载文件的,默认扫描我们当前系统里面所有 META-INF/spring.factories 位置的文件

在我们引入的 jar 包中,有的 jar 包有这个文件,有的 jar 包没有这个文件

image.png

加载的137个就是从这个文件的 “自动配置项” 来加载的

image.png

3、按需开启自动配置项

SpringBoot 提供了按需引入自动配置项的功能,因为若一开始将所有功能直接全部加载到容器中,不管这些功能有用没有,会导致容器沉重且缓慢,占用很多资源,因此 SpringBoot 提供了按需引入

按需引入是怎么实现的?

是利用 @Conditional相关注解和相关的包导入来协同配合

这一切都是得益于 SpringBoot 按需加载的注解

举两个例子

1、aop 功能

image.png

image.png

要有这个包,才有Advice这个类,有这个类,配置才生效

image.png

因为容器中没有 Advice 这个类,所以这个条件不成立,下面的配置不生效,因此这个组件不能导入到容器中。若要开启此功能,要引入 aop 的相关依赖

2、批处理功能

image.png

image.png

相同的,没导入批处理相关的包,就没有这个类,如果没有这个类,经过Condition条件选择,下面的配置就不能生效。其他也是同理!