前言
要说 SpringBoot 有什么优点的话,比较核心的就是简化配置和自动配置了。前面我们分析了 SpringBoot 的启动流程还有一些监听器相关的源码,今天来分析下 SpringBoot 的自动配置具体怎么实现的,以便日后遇到相关错误知道从哪里入手。
自动化配置报错
初学 SpringBoot 的我们在启动自己的第一个 SpringBoot demo 的时候,可能都遇到了以下这个报错:1
2
3
4
5
6
7
8
9
10
11
12Description:
Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured.
Reason: Failed to determine a suitable driver class
Action:
Consider the following:
If you want an embedded database (H2, HSQL or Derby), please put it on the classpath.
If you have database settings to be loaded from a particular profile you may need to activate it (no profiles are currently active).
日志意思很明显是配置 datasource 的时候出错了,我们都没有配置 datasource 呢怎么就报错了。当时心想什么垃圾 SpringBoot!网上搜索以下,在启动类上添加一个注解:1
(exclude = DataSourceAutoConfiguration.class)
或者去掉 pom 文件中的一个依赖:1
2
3
4<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
然后启动成功后暗自高心了一把,SpringBoot 也没什么嘛,这么简单!
自动化配置解析
@EnableAutoConfiguration
上面说到 @EnableAutoConfiguration 注解意思开启自动配置。源码如下:1
2
3
4
5
6
7
8
9
10
11
12
13 (ElementType.TYPE)
(RetentionPolicy.RUNTIME)
(AutoConfigurationImportSelector.class)
public EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
关键点在于用 @Import 导入的 AutoConfigurationImportSelector。该类实现了 ImportSelector 接口,这个接口只有一个方法,代码如下:1
2
3
4
5
6
7
8
9public interface ImportSelector {
/**
* Select and return the names of which class(es) should be imported based on
* the {@link AnnotationMetadata} of the importing @{@link Configuration} class.
*/
String[] selectImports(AnnotationMetadata importingClassMetadata);
}
注释写的很清楚,这个方法返回的 name 对应的 bean 都是要注入到 Spring 容器里的。然后我们再看实现方法:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
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 = filter(configurations, autoConfigurationMetadata);
fireAutoConfigurationImportEvents(configurations, exclusions);
return StringUtils.toStringArray(configurations);
}
其中参数 AnnotationMetadata 封装了注解的相关信息。
这个方法具体在什么调用,我们可以一层层往上看,会发现是在刷新上下文的时候调用。
现在直接看重点 getCandidateConfigurations(annotationMetadata, attributes);
代码如下:1
2
3
4
5
6
7
8
9protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
AnnotationAttributes attributes) {
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;
}
代码很熟悉,在前一篇分析启动流程的时候看到过,主要就是从 META-INF/spring.factories
文件中获取。spring-boot-autoconfigure 包下这个文件中
内置了很多要自动配置的类(但是这些类并不会全部配置,例如前面说到的可以通过 @EnableAutoConfiguration(exclude = XXX.class)
去排除),其中就包括一个叫 DataSourceAutoConfiguration 的类。下面以这个类举例。
DataSourceAutoConfiguration
这个类上有几个注解:1
2
3
4
5
({ DataSource.class, EmbeddedDatabaseType.class })
(DataSourceProperties.class)
({ DataSourcePoolMetadataProvidersConfiguration.class,
DataSourceInitializationConfiguration.class })
- @Configuration 表示这是一个配置类。
- @ConditionalOnClass 是一个条件,当存在指定的类时该条件才满足,当前类才配置。
- @EnableConfigurationProperties 启用配置属性。
- @Import 导入指定的类。
注意这里 @ConditionalOnClass 条件中的 EmbeddedDatabaseType 类在 spring-jdbc 包里,这也是为什么前面说到去掉 pom 文件里 jdbc 的依赖就不会报错了。因为去掉 jdbc 的依赖后,这里的 @ConditionalOnClass 不满足,DataSourceAutoConfiguration 也就不会自动配置了。
接着看为什么 pom 文件里有 jdbc 依赖并且不配置 datasource 相关信息时就会报错呢?
先看 DataSourceAutoConfiguration 的一个内部类:
1 |
|
这里导入 DataSourceConfiguration 的几个内部类 Hikari、Tomcat、Dbcp2、Generic。但是这里只有 Hikari 上的条件注解是满足的,其相关代码如下:
1 | protected static <T> T createDataSource(DataSourceProperties properties, |
DataSourceProperties 中相关代码如下:
1 | public DataSourceBuilder<?> initializeDataSourceBuilder() { |
当我们在配置文件中配置了相关数据源信息后,这里就会创建一个 HikariDataSource。
最后 DataSourceAutoConfiguration 上还会通过注解注入其他的相关配置类,例如后置处理器什么的。
自定义 starter
springboot 开箱即用的特点是因为它针对现在的开发技术提供了很多现成的starter,我们可以理解为一种服务。
自动化配置功能就是 spring-boot-autoconfigure
这个 starter 为我们提供的。下面我们自定义一个 starter 看看它是怎么实现的。
创建一个 starter 项目
首先我们需要创建一个简单的自动配置类 MyRunnableAutoConfiguration 和一个 MyRunnable 类:
1 |
|
1 | public class MyRunnable implements Runnable { |
这里 @ConditionalOnClass 条件很明显是满足的。
然后用 @Value 注入我们配置的名称,没有则是 null。
最后创建一个 MyRunnable 对象。
引入自定义 starter 并测试 MyRunnable
我们在原来的项目中添加自定义的 starter 依赖:
1 | <!-- 自定义 starter --> |
然后在 resources 目录下创建目录 META-INF, 再在里面创建 spring.factories 文件并配置:
1 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ |
最后在启动类中添加测试代码:
1 | ConfigurableApplicationContext context = application.run(args); |
启动项目,控制台打印MyRunnable{name='myRunnable'}
。证明我们自定义的 starter 中的自动配置类配置 MyRunnable 成功。名称默认为 myRunnable。
我们在 yml 配置文件中添加配置:
1 | myRunnableName: myRunnable12345 |
重新启动,控制台打印MyRunnable{name='myRunnable12345'}
。证明我们的配置生效。
总结
至此,我们分析了 spring-boot-autoconfigure
是怎么通过 @EnableAutoConfiguration 注解帮我完成自动配置的。以及实现了一个自定义的 starter 并自动配置我们需要的 bean。