开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第18天,点击查看活动详情
说明:文中源码为SpringBoot2.7.6版本
1、什么是SpringBoot的自动配置
1.1 定义
SpringBoot自动配置,英文是Auto-Configuration:
-
它是基于你引入的依赖jar包,对SpringBoot应用进行自动配置
-
它为SpringBoot框架的“开箱即用”提供了基础支撑
1.2 区分自动配置和自动装配
区分自动配置和自动装配
自动配置:Auto-Configuration,针对的是SpringBoot中的配置类
自动装配:Autowire,针对的是Spring中的依赖注入
1.3 理解配置类
配置类英文是:Configuration Class
广义的“配置类”:被注解@Component直接或间接修饰的某个类,即我们常说的Spring组件,其中包括了@Configuration类
狭义的“配置类”:特指被注解@Configuration所修饰的某个类,又称为@Configuration类无特殊说明所说的配置类为广义的配置类
1.4 配置类的示例
@Configuration
public class Constant {
@Bean
public String beanData() {
return "bean";
}
}
@Component
@ConfigurationProperties(prefix = "simple")
public class SimpleProperties {
private String env;
public String getEnv() {
return env;
}
public void setEnv(String env) {
this.env = env;
}
}
2、SpringBoot自动配置的示例 - Redis
2.1 引入依赖
pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2.2 配置Redis
application.yml
spring:
redis:
database: 0
host: 127.0.0.1
port: 6379
password:
2.3 使用Redis
@Autowired
private RedisTemplate<Object, Object> redisTemplate;
@Autowired
private StringRedisTemplate springRedisTemplate;
3、SpringBoot的启动流程
3.1 SpringBoot启动的简化版代码
public static void run(Class<?> primaryClass) {
// 1. 创建一个ApplicationContext实例,即IoC容器
ApplicationContext contet = createApplicationContext();
// 2. 将主类(primaryClass)注册到IoC容器中
loadSourceClass(context, primaryClass);
// 3. 递归加载并处理所有的配置类
processConfigurationClasses(context);
// 4. 实例化所有的单例Bean(Singleton Bean)
instantiateSingletonBeans(context);
// 5. 如果是Web应用,则启动Web服务器
startWebServer(context);
}
3.2 SpringBoot启动的流程
说明:
- 第2步,源配置类:通常是main方法所在的类,而且会被注解@SpringBootApplication所修饰,又称之为主类,这一步很简单但是又很重要,它是遍历所有配置类的起点;
- 第3步,SpringBoot会自动找到所有的配置类,然后加载和处理它们,“自动配置”就属于其中一环;
- 第4步,实例化所有的单例Bean,“依赖注入”和“自动装配”就属于其中的环节;
3.3 加载配置类的简化版代码1
public static void processConfigurationClasses(ApplicationContext context) {
// 1. 首先从IoC容器中取出当前存在的源配置类
Class<?> sourceConfigurationClass = getSourceConfigurationClass(context);
// 2. 创建一个配置类解析器,然后递归加载并处理应用中所有的配置类
ConfigClassParser parser = new ConfigClassParser(context);
parser.parse(sourceConfigurationClass);
// 3.1 向IoC容器中注册@Bean方法对应的BeanDefinition
loadBeanDefinitionsFromBeanMethods(parser.configurationClasses);
// 3.2 向IoC容器中注册ImportBeanDefinitionRegistrar导入的BeanDefinition
loadBeanDefinitionsFromRegistrars(parser.configurationClasses);
}
3.4 加载配置类的简化版代码2
public void parse(Class<?> configClass) {
// 1. 处理@ComponentScan:根据@ComponentScan扫描指定的package,得到一系列配置类
if (hasComponentScan(configClass)) {
for (Class<?> clazz : doScan(configClass)) {
// 递归处理
this.parse(clazz);
}
}
// 2. 处理注解@Import:根据注解@Import,得到一系列被导入的配置类
if (hasIimportedClasses(configClass)) {
for (Class<?> clazz : getImports(configClass)) {
// 递归处理
this.parse(clazz);
}
}
// 3. 处理@Bean方法
processBeanMethods(configClass);
// 4. 处理@Import导入的ImportBeanDefinitionRegistrar
processRegistrars(configClass);
// 5. 加入到一个全局的配置类集合中
this.configurationClasses.add(configClass);
}
3.5 加载配置类的流程
这一步涉及到了两个重要的注解@ComponentScan和@Import,SpringBoot使用注解@Import导入一个ImportSelector从而实现了自动配置功能,下面从源码了解一下。
4、一切都从注解@SpringBootApplication说起
4.1 注解@SpringBootApplication源码
每个SpringBoot项目的都有一个启动类,该类关键的一点是要有注解@SpringBootApplication
@SpringBootApplication
public class Springboot12Application {
public static void main(String[] args) {
SpringApplication.run(Springboot12Application.class, args);
}
}
下面我们就看一下注解@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 {
......
}
再看一下注解@SpringBootConfigiguration
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
@Indexed
public @interface SpringBootConfiguration {
......
}
再看一下注解@EnableAutoConfiguration
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
......
}
说明:
- 其中AutoConfigurationImportSelector的含义是开启了自动配置的功能
4.2 注解@SpringBootApplication结构图
说明
- @SpringBootApplication修改的类,也会被@Configuration间接修饰,即“源配置类”
- SpringBoot框架会对“源配置类”所在的package进行组件扫描(Component Scan)
- SpringBoot框架最终会导入AutoConfigurationImportSelector来实现自动配置
5、如何实现AutoConfigurationImportSelector类
AutoConfigurationImportSelector应该如何实现,才能优雅地去发现classpath中的jar包的自动配置类?
所谓“优雅”,指的是用记只需引入jar包即可,至于jar包中有哪些自动配置类、类名是什么等乖这些信息都不用关心。这个特性和Java SPI类似,Spring框架中的SpringFactories机制,它就是Java SPI设计思想的延伸和扩展。
5.1 SpringFactories机制
- Java SPI机制的延伸和扩展
- Spring框架的基础机制,在Spring以及SpringBoot源码中到处可见
- 可以基于它来实现SpringBoot的自动配置功能
- 它的核心逻辑是从classpath中读取到所有jar包中的配置文件META-IF/spring.factories,然后根据指定的key从配置文件中解析出对应的value值
5.2 Java SPI机制 vs SpringFactories机制
| Java SPI机制 | SpringFactories机制 |
|---|---|
| 使用约定的配置文件: 文件路径是META-IF/services/<Service接口全限定名> 文件内容是Service Provider类的全限定名。多个Provider类的话,每个类占据一行 | 使用约定的配置文件 文件路径是META-IF/spring.factories 文件内容是"key=value1,valu2,...valueN"的格式。其中key是指定的某个类名,value是逗号隔开的多个类名 |
| 第三方jar包,负责提供配置文件: 高内聚低耦合,代码配置一肩挑 使用ClassLoader来读取classpath的配置文件 | 同左 |
| 通过ServiceLoader,返回一个ServiceProvider的对象实例集合 | 通过类SpringFactoriesLoader,返回一个类名的集合,可以根据实际需求对这些类名进行下一步的处理 |
5.3 AutoConfigurationImportSelector关键源码
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
// SpringBoot自动配置的入口方法
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
// 1. 获取 annotationMetadata 的注解 @EnableAutoConfiguration 的属性
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 2. 从资源文件 spring.factories 中获取 @EnableAutoConfiguration 对应的所有的类
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
configurations = removeDuplicates(configurations);
// 3. 通过在注解 @EnableAutoConfiguration 设置 exclude 的相关属性,可以排除指定的自动配置类
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
// 4. 根据注解 @Conditional 来判断是否需要排除某些自动配置类
configurations = getConfigurationClassFilter().filter(configurations);
// 5. 触发 AutoConfiguration 导入的相关事件
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
// 通过 SpringFactories 机制,从配置文件 spring.factories 中批出所有的自动配置类
List<String> configurations = new ArrayList<>(
SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()));
ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader()).forEach(configurations::add);
Assert.notEmpty(configurations,
"No auto configuration classes found in META-INF/spring.factories nor in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you "
+ "are using a custom packaging, make sure that file is correct.");
return configurations;
}
5.4 注解@Conditional
@Conditional,是来自Spring框架的一个注解
- 它的作用是实现:只有在特定条件满足时,才会向IoC容器注册指定的组件
- 可以将@Conditional理解为某种情况下的IF语句
Spring框架提供了一系列常用的Conditional扩展注解,如下
| 注解名称 | 作用 |
|---|---|
| @ConditionalOnBean | 当容器中存在指定的Bean时,满足条件 |
| @ConditionalOnMissingBean | 当容器中不存在指定的Bean时,满足条件 |
| @ConditionalOnClass | 当classpath中存在指定的类时,满足条件 |
| @ConditionalOnMissingClass | 当classpath中不存在指定的类时,满足条件 |
| @ConditionalOnProperty | 当指定的属性具备指定的值时,满足条件 |
| @ConditionalOnWebApplication | 当应用程序是web应用时,满足条件 |
5.5 AutoConfigurationImportSelector实现流程
说明:
- 第1步,通过ClassLoader去获取classpath中的配置文件META-INF/spring.factories
- 第2步,在所有的配置文件META-INF/spring.factories中,筛选出以EnableAutoConfiguration.class为key的、符合条件的配置类
- 第3步,注解@Conditional,提供了一种灵活的过滤机制