SpringBoot 的自动配置原理

课程在讲解 SpringBoot 的自动配置原理时,@ComponentScan 中默认的两个过滤器没有深入讲解,@AutoConfigurationPackage 的作用讲解得有问题,因此这里做一个扩展和订正。

@ComponentScan 中的两个过滤器有什么用?

@SpringBootApplication 注解是 @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan 三个注解的组合/合成注解,如下:

1
2
3
4
5
6
7
8
9
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }
)
public @interface SpringBootApplication {
// ...
}

注意到 @ComponentScan 没有为 basePackages 属性提供值,此时会默认扫描该注解所标注的类所在的包及其子包下的所有 @Component @Controller @Service @Repository 注解所标注的类。

可以看到 @ComponentScanexcludeFilters 还提供了两个过滤器,满足过滤器条件的将会被排出扫描:

  • TypeExcludeFilter:用于排除特定类型的组件,默认情况下会排除 BeanFactory类。

BeanFactory 是 Spring 框架中用于管理 Bean 的核心接口,通常不希望将其作为普通的 Bean 注册到容器中。

  • AutoConfigurationExcludeFilter:用于排除自动配置类。

@SpringBootApplication 中没有为 @ComponentScan 提供 basePackages 属性,因此是排除主程序所在的包及其子包下的所有自动配置类。

  • 什么是自动配置类?

使用 @Configuration 注解标注的类是一个配置类,如果该配置类,还在类路径下的 META-INF/spring.factories 文件中,进行了注册,那么该配置类就是一个自动配置类。

具体怎么注册,其实就是将该配置类的类名放在 org.springframework.boot.autoconfigure.EnableAutoConfiguration 这个键的后面,比如 spring-boot-autoconfigure-2.3.4.RELEASE.jar 包下的 META-INF/spring.factories 就注册了以下配置类为自动配置类:

1
2
3
4
5
6
7
8
9
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
...

我们想要定义一个自动配置类的话,假设该类的全类名为 com.atguigu.boot.MyAutoConfiguration,那么首先该类要用 @Configuration 进行标注,然后要在 resources 目录下创建 META-INF/spring.factories 文件,并将该类的全类名进行注册:

1
2
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.atguigu.boot.MyAutoConfiguration

自动配置类的命名一般格式为 XxxxAutoConfiguration

当一个类是自动配置类的时候,那么在 SpringBoot 启动时,会调用 SpringFactoriesLoader 去读取类路径下,包括依赖 jar 包下,所有 META-INF/spring.factories 文件中的信息,将信息封装到一个 Map<String, List<String>> 中。

比如,META-INF/spring.factories 文件中的自动配置类信息就会以 org.springframework.boot.autoconfigure.EnableAutoConfiguration 为 key,以下面所有注册的自动配置类的全类名为 value,填入 Map 集合中。

  • 为什么要排除自动配置类?

一般来说,@SpringBootApplication 包含的三个注解中,@ComponentScan 注解早于 @EnableAutoConfiguration 执行,在 @EnableAutoConfiguration 内部又包含两个注解:

1
2
3
4
5
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
// ...
}

其中 @Import(AutoConfigurationImportSelector.class) 负责将自动配置类导入/注册到容器中。

因此,我们在 @ComponentScan 执行时,就应该将自动配置类的注册工作让出来,讲给后续 @EnableAutoConfiguration 执行时,底层的 @Import(AutoConfigurationImportSelector.class) 来完成。

@AutoConfigurationPackage 底层做了什么,到底有什么用,其功能是否和 @ComponentScan 有重叠?

  • 先来看 @AutoConfigurationPackage 底层做了什么
1
2
3
4
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
// ...
}

其实 @AutoConfigurationPackage 底层做的工作很简单,就是检查当前容器的 BeanDefinitionRegistry 中是否已经注册了名字为 org.springframework.boot.autoconfigure.AutoConfigurationPackages 的 Bean 定义。

如果没有,一般来说启动时是没有注册的,此时就进行注册。

这个要注册 Bean 定义的类型为 AutoConfigurationPackages.BasePackages,其内部就是封装了一个包路径数组,在这里,BasePackages 内部数组只包含一个路径 com.atguigu.boot,就是主程序所在的包。

可以说,@AutoConfigurationPackage 底层相当于是将 com.atguigu.boot 标记为一个自动配置包。

  • 所以这有什么用呢?

自动配置类在执行时,可能利用 BasePackages 中的自动配置包路径,来完成组件的注册之类的工作。

比如说,我们在自己应用中使用了 JPA 技术,com.atguigu.boot 包下存在一些用 @Entity 标注的 JPA 的实体类。

此时 JPA 相关的自动配置类,通过 BasePackages 中的自动配置包路径 com.atguigu.boot,就可以找到这些用 @Entity 标注的实体类,并将这些实体类注册到 Spring 中。

同理,如果我们使用了 MybatisPlus 技术,那么 MybatisPlus 相关的自动配置类,就可以找到 com.atguigu.boot 包下的所有 @Mapper 标注的 Mapper 接口,并将这些接口的代理注册到 Spring 中。

  • @AutoConfigurationPackage 功能是否和 @ComponentScan 有重叠?

@AutoConfigurationPackage 是将自动配置包路径绑定到 Spring 中,方便后续自动配置类扫描这些路径,并注册这些路径中的组件,这些组件一般来说都是用 @Component @Controller @Service @Repository 以外的注解来进行标注的。

@ComponentScan 是扫描指定包下,所有用 @Component @Controller @Service @Repository 注解标注的组件。

可以说,它们的效果有些类似,但它们的注册的对象是不同的!

参考博客