前言
要说 SpringBoot 有什么优点的话,比较核心的就是简化配置和自动配置了。前面我们分析了 SpringBoot 的启动流程还有一些监听器相关的源码,
今天来分析下 SpringBoot 的自动配置具体怎么实现的,以便日后遇到相关错误知道从哪里入手。
自动化配置报错
初学 SpringBoot 的我们在启动自己的第一个 SpringBoot demo 的时候,可能都遇到了以下这个报错:
Description:
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!网上搜索以下,在启动类上添加一个注解:
@EnableAutoConfiguration(exclude = DataSourceAutoConfiguration.class)
或者去掉 pom 文件中的一个依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
然后启动成功后暗自高心了一把,SpringBoot 也没什么嘛,这么简单!
自动化配置解析
@EnableAutoConfiguration
上面说到 @EnableAutoConfiguration 注解意思开启自动配置。源码如下:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented@Inherited@AutoConfigurationPackage@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
StringENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}关键点在于用 @import 导入的 AutoConfigurationImportSelector。该类实现了 ImportSelector 接口,这个接口只有一个方法,代码如下:
publicinterfaceImportSelector {
/** * 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(AnnotationMetadataimportingClassMetadata);
}注释写的很清楚,这个方法返回的 name 对应的 bean 都是要注入到 Spring 容器里的。然后我们再看实现方法:
@OverridepublicString[] selectImports(AnnotationMetadataannotationMetadata) {
if (!isEnabled(annotationMetadata)) {
returnNO_IMPORTS;
}
AutoConfigurationMetadataautoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
AnnotationAttributesattributes = 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);
returnStringUtils.toStringArray(configurations);
}其中参数 AnnotationMetadata 封装了注解的相关信息。
这个方法具体在什么调用,我们可以一层层往上看,会发现是在刷新上下文的时候调用。
现在直接看重点 getCandidateConfigurations(annotationMetadata, attributes); 代码如下:
protectedList<String> getCandidateConfigurations(AnnotationMetadatametadata,
AnnotationAttributesattributes) {
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.");
returnconfigurations;
}代码很熟悉,在前一篇分析启动流程的时候看到过,主要就是从 META-INF/spring.factories 文件中获取。spring-boot-autoconfigure 包下这个文件中
内置了很多要自动配置的类(但是这些类并不会全部配置,例如前面说到的可以通过 @EnableAutoConfiguration(exclude = XXX.class) 去排除),其中就包括一个叫 DataSourceAutoConfiguration 的类。下面以这个类举例。
DataSourceAutoConfiguration
这个类上有几个注解:
@Configuration@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
@EnableConfigurationProperties(DataSourceProperties.class)
@Import({ DataSourcePoolMetadataProvidersConfiguration.class,
DataSourceInitializationConfiguration.class })- @configuration 表示这是一个配置类。
- @ConditionalOnClass 是一个条件,当存在指定的类时该条件才满足,当前类才配置。
- @EnableConfigurationProperties 启用配置属性。
- @import 导入指定的类。
注意这里 @ConditionalOnClass 条件中的 EmbeddedDatabaseType 类在 spring-jdbc 包里,这也是为什么前面说到去掉 pom 文件里 jdbc 的依赖就不会报错了。因为去掉 jdbc 的依赖后,这里的 @ConditionalOnClass 不满足,DataSourceAutoConfiguration 也就不会自动配置了。
接着看为什么 pom 文件里有 jdbc 依赖并且不配置 datasource 相关信息时就会报错呢?
先看 DataSourceAutoConfiguration 的一个内部类:
@Configuration@Conditional(PooledDataSourceCondition.class)
@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
@Import({ DataSourceConfiguration.Hikari.class, DataSourceConfiguration.Tomcat.class,
DataSourceConfiguration.Dbcp2.class, DataSourceConfiguration.Generic.class,
DataSourceJmxConfiguration.class })
protectedstaticclassPooledDataSourceConfiguration {
}这里导入 DataSourceConfiguration 的几个内部类 Hikari、Tomcat、Dbcp2、Generic。但是这里只有 Hikari 上的条件注解是满足的,其相关代码如下:
protectedstatic <T> TcreateDataSource(DataSourcePropertiesproperties,
Class<? extendsDataSource> type) {
return (T) properties.initializeDataSourceBuilder().type(type).build();
}
/** * Hikari DataSource configuration. */@ConditionalOnClass(HikariDataSource.class)
@ConditionalOnMissingBean(DataSource.class)
@ConditionalOnProperty(name = "spring.datasource.type", havingValue = "com.zaxxer.hikari.HikariDataSource", matchIfMissing = true)
staticclassHikari {
@Bean@ConfigurationProperties(prefix = "spring.datasource.hikari")
publicHikariDataSourcedataSource(DataSourcePropertiesproperties) {
// 创建 HikariDataSourceHikariDataSourcedataSource = createDataSource(properties,
HikariDataSource.class);
if (StringUtils.hasText(properties.getName())) {
dataSource.setPoolName(properties.getName());
}
returndataSource;
}
}DataSourceProperties 中相关代码如下:
publicDataSourceBuilder<?> initializeDataSourceBuilder() {
returnDataSourceBuilder.create(getClassLoader()).type(getType())
.driverClassName(determineDriverClassName()).url(determineUrl())
.username(determineUsername()).password(determinePassword());
}
publicStringdetermineDriverClassName() {
if (StringUtils.hasText(this.driverClassName)) {
Assert.state(driverClassIsLoadable(),
() -> "Cannot load driver class: " + this.driverClassName);
returnthis.driverClassName;
}
StringdriverClassName = null;
if (StringUtils.hasText(this.url)) {
driverClassName = DatabaseDriver.fromJdbcUrl(this.url).getDriverClassName();
}
if (!StringUtils.hasText(driverClassName)) {
driverClassName = this.embeddedDatabaseConnection.getDriverClassName();
}
// 这里就是前言自动化配置报错的地方,同理配置了驱动但是不配置url也会报错if (!StringUtils.hasText(driverClassName)) {
thrownewDataSourceBeanCreationException(
"Failed to determine a suitable driver class", this,
this.embeddedDatabaseConnection);
}
returndriverClassName;
}当我们在配置文件中配置了相关数据源信息后,这里就会创建一个 HikariDataSource。
最后 DataSourceAutoConfiguration 上还会通过注解注入其他的相关配置类,例如后置处理器什么的。
自定义 starter
springboot 开箱即用的特点是因为它针对现在的开发技术提供了很多现成的starter,我们可以理解为一种服务。
自动化配置功能就是 spring-boot-autoconfigure 这个 starter 为我们提供的。下面我们自定义一个 starter 看看它是怎么实现的。
创建一个 starter 项目
首先我们需要创建一个简单的自动配置类 MyRunnableAutoConfiguration 和一个 MyRunnable 类:
@Configuration@ConditionalOnClass(MyRunnable.class)
publicclassMyRunnableAutoConfiguration {
@Value("${myRunnableName:#{null}}")
StringmyRunnableName;
@BeanpublicMyRunnablemyRunnable() {
returnnewMyRunnable(myRunnableName);
}
}publicclassMyRunnableimplementsRunnable {
privatestaticfinalStringDEFAULT_NAME = "myRunnable";
privateStringname;
publicMyRunnable(Stringname) {
this.name = (name == null ? DEFAULT_NAME : name);
}
@Overridepublicvoidrun() {
System.out.println(this.toString());
}
@OverridepublicStringtoString() {
return"MyRunnable{" +
"name='" + name + '\'' +
'}';
}
}引入自定义 starter 并测试 MyRunnable
我们在原来的项目中添加自定义的 starter 依赖:
<!-- 自定义 starter -->
<dependency>
<groupId>com.lollipop</groupId>
<artifactId>my-starter</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
然后在 resources 目录下创建目录 META-INF, 再在里面创建 spring.factories 文件并配置:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.lollipop.starter.MyRunnableAutoConfiguration
最后在启动类中添加测试代码:
ConfigurableApplicationContextcontext = application.run(args);
//获取 my-starter 项目中定义的bean runnableRunnablerunnable = (Runnable) context.getBean("myRunnable");
runnable.run();启动项目,控制台打印MyRunnable{name='myRunnable'}。证明我们自定义的 starter 中的自动配置类配置 MyRunnable 成功。名称默认为 myRunnable。
我们在 yml 配置文件中添加配置:
myRunnableName: myRunnable12345
重新启动,控制台打印MyRunnable{name='myRunnable12345'}。证明我们的配置生效。
总结
至此,我们分析了 spring-boot-autoconfigure 是怎么通过 @EnableAutoConfiguration 注解帮我完成自动配置的。以及实现了一个自定义的 starter 并自动配置我们需要的 bean。
前言
要说 SpringBoot 有什么优点的话,比较核心的就是简化配置和自动配置了。前面我们分析了 SpringBoot 的启动流程还有一些监听器相关的源码,
今天来分析下 SpringBoot 的自动配置具体怎么实现的,以便日后遇到相关错误知道从哪里入手。
自动化配置报错
初学 SpringBoot 的我们在启动自己的第一个 SpringBoot demo 的时候,可能都遇到了以下这个报错:
日志意思很明显是配置 datasource 的时候出错了,我们都没有配置 datasource 呢怎么就报错了。当时心想什么垃圾 SpringBoot!网上搜索以下,在启动类上添加一个注解:
或者去掉 pom 文件中的一个依赖:
然后启动成功后暗自高心了一把,SpringBoot 也没什么嘛,这么简单!
自动化配置解析
@EnableAutoConfiguration
上面说到 @EnableAutoConfiguration 注解意思开启自动配置。源码如下:
关键点在于用 @import 导入的 AutoConfigurationImportSelector。该类实现了 ImportSelector 接口,这个接口只有一个方法,代码如下:
注释写的很清楚,这个方法返回的 name 对应的 bean 都是要注入到 Spring 容器里的。然后我们再看实现方法:
其中参数 AnnotationMetadata 封装了注解的相关信息。
这个方法具体在什么调用,我们可以一层层往上看,会发现是在刷新上下文的时候调用。
现在直接看重点
getCandidateConfigurations(annotationMetadata, attributes);代码如下:代码很熟悉,在前一篇分析启动流程的时候看到过,主要就是从
META-INF/spring.factories文件中获取。spring-boot-autoconfigure 包下这个文件中内置了很多要自动配置的类(但是这些类并不会全部配置,例如前面说到的可以通过
@EnableAutoConfiguration(exclude = XXX.class)去排除),其中就包括一个叫 DataSourceAutoConfiguration 的类。下面以这个类举例。DataSourceAutoConfiguration
这个类上有几个注解:
注意这里 @ConditionalOnClass 条件中的 EmbeddedDatabaseType 类在 spring-jdbc 包里,这也是为什么前面说到去掉 pom 文件里 jdbc 的依赖就不会报错了。因为去掉 jdbc 的依赖后,这里的 @ConditionalOnClass 不满足,DataSourceAutoConfiguration 也就不会自动配置了。
接着看为什么 pom 文件里有 jdbc 依赖并且不配置 datasource 相关信息时就会报错呢?
先看 DataSourceAutoConfiguration 的一个内部类:
这里导入 DataSourceConfiguration 的几个内部类 Hikari、Tomcat、Dbcp2、Generic。但是这里只有 Hikari 上的条件注解是满足的,其相关代码如下:
DataSourceProperties 中相关代码如下:
当我们在配置文件中配置了相关数据源信息后,这里就会创建一个 HikariDataSource。
最后 DataSourceAutoConfiguration 上还会通过注解注入其他的相关配置类,例如后置处理器什么的。
自定义 starter
springboot 开箱即用的特点是因为它针对现在的开发技术提供了很多现成的starter,我们可以理解为一种服务。
自动化配置功能就是
spring-boot-autoconfigure这个 starter 为我们提供的。下面我们自定义一个 starter 看看它是怎么实现的。创建一个 starter 项目
首先我们需要创建一个简单的自动配置类 MyRunnableAutoConfiguration 和一个 MyRunnable 类:
这里 @ConditionalOnClass 条件很明显是满足的。
然后用 @value 注入我们配置的名称,没有则是 null。
最后创建一个 MyRunnable 对象。
引入自定义 starter 并测试 MyRunnable
我们在原来的项目中添加自定义的 starter 依赖:
然后在 resources 目录下创建目录 META-INF, 再在里面创建 spring.factories 文件并配置:
最后在启动类中添加测试代码:
启动项目,控制台打印
MyRunnable{name='myRunnable'}。证明我们自定义的 starter 中的自动配置类配置 MyRunnable 成功。名称默认为 myRunnable。我们在 yml 配置文件中添加配置:
重新启动,控制台打印
MyRunnable{name='myRunnable12345'}。证明我们的配置生效。总结
至此,我们分析了
spring-boot-autoconfigure是怎么通过 @EnableAutoConfiguration 注解帮我完成自动配置的。以及实现了一个自定义的 starter 并自动配置我们需要的 bean。