寫在前面
這個demo來說明怎麼一步步排查一個常見的spring boot AutoConfiguration的錯誤。
https://github.com/hengyunabc/spring-boot-inside/tree/master/demo-database-type-NONE
調試排查 Cannot determine embedded database driver class for database type NONE 的錯誤
把工程導入IDE裏,直接啓動應用,拋出來的異常信息是:
Error starting ApplicationContext. To display the auto-configuration report re-run your application with 'debug' enabled.
2017-11-29 14:26:34.478 ERROR 29736 --- [ main] o.s.b.d.LoggingFailureAnalysisReporter :
***************************
APPLICATION FAILED TO START
***************************
Description:
Cannot determine embedded database driver class for database type NONE
Action:
If you want an embedded database please put a supported one on the classpath. If you have database settings to be loaded from a particular profile you may need to active it (no profiles are currently active).
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
其實這時有兩個思路,直接google搜索Cannot determine embedded database driver class for database type NONE
,就可以找到解決辦法。
第二種方式,仔細查看日誌內容,可以發現有To display the auto-configuration report re-run your application with 'debug' enabled.
。
搜索下這個,就可以在spring的官方網站上找到相關的信息:https://docs.spring.io/spring-boot/docs/current/reference/html/using-boot-auto-configuration.html
就是用戶只要配置了debug
這個開關,就會把auto-configuration
相關的信息打印出來。
熟悉spring的環境變量注入的話,就可以知道有幾種打開這個的方式:
- 在
args
裏增加--debug
- 在application.properties裏增加
debug=true
- 通過
-Ddebug=true
增加debug
開關之後的信息
增加debug
開關之後,可以看到打印出了錯誤堆棧:
2017-11-29 14:33:08.776 DEBUG 29907 --- [ main] o.s.b.d.LoggingFailureAnalysisReporter : Application failed to start due to an exception
org.springframework.boot.autoconfigure.jdbc.DataSourceProperties$DataSourceBeanCreationException: Cannot determine embedded database driver class for database type NONE. If you want an embedded database please put a supported one on the classpath. If you have database settings to be loaded from a particular profile you may need to active it (no profiles are currently active).
at org.springframework.boot.autoconfigure.jdbc.DataSourceProperties.determineDriverClassName(DataSourceProperties.java:245) ~[spring-boot-autoconfigure-1.4.7.RELEASE.jar:1.4.7.RELEASE]
at org.springframework.boot.autoconfigure.jdbc.DataSourceProperties.initializeDataSourceBuilder(DataSourceProperties.java:182) ~[spring-boot-autoconfigure-1.4.7.RELEASE.jar:1.4.7.RELEASE]
at org.springframework.boot.autoconfigure.jdbc.DataSourceConfiguration.createDataSource(DataSourceConfiguration.java:42) ~[spring-boot-autoconfigure-1.4.7.RELEASE.jar:1.4.7.RELEASE]
at org.springframework.boot.autoconfigure.jdbc.DataSourceConfiguration$Tomcat.dataSource(DataSourceConfiguration.java:53) ~[spring-boot-autoconfigure-1.4.7.RELEASE.jar:1.4.7.RELEASE]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_112]
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
拋出異常的代碼是:
/**
* Determine the driver to use based on this configuration and the environment.
* @return the driver to use
* @since 1.4.0
*/
public String determineDriverClassName() {
if (StringUtils.hasText(this.driverClassName)) {
Assert.state(driverClassIsLoadable(),
"Cannot load driver class: " + this.driverClassName);
return this.driverClassName;
}
String driverClassName = null;
if (StringUtils.hasText(this.url)) {
driverClassName = DatabaseDriver.fromJdbcUrl(this.url).getDriverClassName();
}
if (!StringUtils.hasText(driverClassName)) {
driverClassName = this.embeddedDatabaseConnection.getDriverClassName();
}
if (!StringUtils.hasText(driverClassName)) {
throw new DataSourceBeanCreationException(this.embeddedDatabaseConnection,
this.environment, "driver class");
}
return driverClassName;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
可以看出來是沒有找到 DataSource 的driver class,然後拋出了 DataSourceBeanCreationException
。
那麼一種解決辦法是,在maven依賴里加入一些 DataSource driver class。
但是應用自己的代碼裏並沒有使用DataSource,哪裏導致spring boot要創建一個DataSource對象?
哪裏導致spring boot要創建DataSource
從異常棧上,可以找到DataSourceConfiguration$Tomcat
這個類,那麼查找下它的引用,可以發現它是被org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration.PooledDataSourceConfiguration
import引入的。
@Configuration
@Conditional(PooledDataSourceCondition.class)
@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
@Import({ DataSourceConfiguration.Tomcat.class, DataSourceConfiguration.Hikari.class,
DataSourceConfiguration.Dbcp.class, DataSourceConfiguration.Dbcp2.class,
DataSourceConfiguration.Generic.class })
protected static class PooledDataSourceConfiguration {
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
那麼 PooledDataSourceConfiguration
是怎麼生效的呢?從代碼上可以看到@Conditional(PooledDataSourceCondition.class)
。
那麼再看PooledDataSourceCondition
的具體實現:
/**
* {@link AnyNestedCondition} that checks that either {@code spring.datasource.type}
* is set or {@link PooledDataSourceAvailableCondition} applies.
*/
static class PooledDataSourceCondition extends AnyNestedCondition {
PooledDataSourceCondition() {
super(ConfigurationPhase.PARSE_CONFIGURATION);
}
@ConditionalOnProperty(prefix = "spring.datasource", name = "type")
static class ExplicitType {
}
@Conditional(PooledDataSourceAvailableCondition.class)
static class PooledDataSourceAvailable {
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
PooledDataSourceCondition
引入了@Conditional(PooledDataSourceAvailableCondition.class)
:
/**
* {@link Condition} to test if a supported connection pool is available.
*/
static class PooledDataSourceAvailableCondition extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
ConditionMessage.Builder message = ConditionMessage
.forCondition("PooledDataSource");
if (getDataSourceClassLoader(context) != null) {
return ConditionOutcome
.match(message.foundExactly("supported DataSource"));
}
return ConditionOutcome
.noMatch(message.didNotFind("supported DataSource").atAll());
}
/**
* Returns the class loader for the {@link DataSource} class. Used to ensure that
* the driver class can actually be loaded by the data source.
* @param context the condition context
* @return the class loader
*/
private ClassLoader getDataSourceClassLoader(ConditionContext context) {
Class<?> dataSourceClass = new DataSourceBuilder(context.getClassLoader())
.findType();
return (dataSourceClass == null ? null : dataSourceClass.getClassLoader());
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
從代碼裏,可以看到是嘗試查找dataSourceClass
,如果找到,條件就成立。那麼debug下,可以發現查找到的dataSourceClass
是:org.apache.tomcat.jdbc.pool.DataSource
。
那麼再看下org.apache.tomcat.jdbc.pool.DataSource
這個類是從哪裏來的呢?
從maven依賴樹可以看到,依賴是來自:spring-boot-starter-jdbc
。所以是應用依賴了spring-boot-starter-jdbc
,但是並沒有配置DataSource
引起的問題。
問題解決辦法
有兩種:
- 沒有使用到
DataSource
,則可以把spring-boot-starter-jdbc
的依賴去掉,這樣就不會觸發spring boot相關的代碼 - 把spring boot自動初始化
DataSource
相關的代碼禁止掉
禁止的辦法有兩種:
在main函數上配置exclude
@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class, DataSourceTransactionManagerAutoConfiguration.class })在application.properties裏配置:
spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration
總結
- 應用沒有使用到
DataSource
,但是在pom.xml裏引入了spring-boot-starter-jdbc
spring-boot-starter-jdbc
帶入了tomcat-jdbc
,它裏面有org.apache.tomcat.jdbc.pool.DataSource
- spring boot裏的
PooledDataSourceConfiguration
,判斷classpath下面有DataSource
的實現類,嘗試去創建DataSource
bean - 在初始化
DataSourceProperties
時,嘗試通過jdbc的url來探測driver class - 因爲應用並沒有配置url,所以最終在
DataSourceProperties.determineDriverClassName()
裏拋出Cannot determine embedded database driver class for database type NONE
最後:
- 排查spring boot的AutoConfiguration問題時,可以按異常棧,一層層排查
Configuration
是怎麼引入的,再排查Condition
具體的判斷代碼。