一、配置环境
1. 配置pom.xml
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
2、自动配置图解
二、相关代码解析
1、自动配置入口: DataSourceAutoConfiguration
@AutoConfiguration(before = SqlInitializationAutoConfiguration.class)
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
@ConditionalOnMissingBean(type = "io.r2dbc.spi.ConnectionFactory")
@EnableConfigurationProperties(DataSourceProperties.class)
@Import({ DataSourcePoolMetadataProvidersConfiguration.class, DataSourceCheckpointRestoreConfiguration.class })
public class DataSourceAutoConfiguration {
@Configuration(proxyBeanMethods = false)
@Conditional(EmbeddedDatabaseCondition.class)
@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
@Import(EmbeddedDataSourceConfiguration.class)
protected static class EmbeddedDatabaseConfiguration {
}
//加载条件: 存在DataSource类且未自定义DataSource Bean
@Configuration(proxyBeanMethods = false)
@Conditional(PooledDataSourceCondition.class)
@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
@Import({ DataSourceConfiguration.Hikari.class, DataSourceConfiguration.Tomcat.class,
DataSourceConfiguration.Dbcp2.class, DataSourceConfiguration.OracleUcp.class,
DataSourceConfiguration.Generic.class, DataSourceJmxConfiguration.class })
protected static class PooledDataSourceConfiguration {
@Bean
@ConditionalOnMissingBean(JdbcConnectionDetails.class)
PropertiesJdbcConnectionDetails jdbcConnectionDetails(DataSourceProperties properties) {
return new PropertiesJdbcConnectionDetails(properties);
}
}
}
2、驱动类名推导解析:DataSourceProperties#determineDriverClassName
@ConfigurationProperties("spring.datasource")
public class DataSourceProperties implements BeanClassLoaderAware, InitializingBean {
public String determineDriverClassName() {
String driverClassName = findDriverClassName();
if (!StringUtils.hasText(driverClassName)) {
throw new DataSourceBeanCreationException("Failed to determine a suitable driver class", this,
this.embeddedDatabaseConnection);
}
return driverClassName;
}
String findDriverClassName() {
// 根据用户application.properties 里面的配置。 如spring.datasource.driver-class-name
if (StringUtils.hasText(this.driverClassName)) {
Assert.state(driverClassIsLoadable(), () -> "Cannot load driver class: " + this.driverClassName);
return this.driverClassName;
}
String driverClassName = null;
// 通过application.properties 里面的spring.datasource.url获取
if (StringUtils.hasText(this.url)) {
driverClassName = DatabaseDriver.fromJdbcUrl(this.url).getDriverClassName();
}
if (!StringUtils.hasText(driverClassName)) {
driverClassName = this.embeddedDatabaseConnection.getDriverClassName();
}
return driverClassName;
}
}
3、驱动名称解析. DatabaseDriver
public enum DatabaseDriver {
public static DatabaseDriver fromJdbcUrl(String url) {
// 通过配置的url 解析: url: jdbc:mysql://127.0.0.1:3306/user?useUnicode=true&characterEncoding=utf8
if (StringUtils.hasLength(url)) {
Assert.isTrue(url.startsWith("jdbc"), "'url' must start with \"jdbc\"");
String urlWithoutPrefix = url.substring("jdbc".length()).toLowerCase(Locale.ENGLISH);
for (DatabaseDriver driver : values()) {
for (String urlPrefix : driver.getUrlPrefixes()) {
String prefix = ":" + urlPrefix + ":";
if (driver != UNKNOWN && urlWithoutPrefix.startsWith(prefix)) {
return driver;
}
}
}
}
return UNKNOWN;
}
}
4、数据源、连接池配置: HikariDataSource
abstract class DataSourceConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(HikariDataSource.class)
@ConditionalOnMissingBean(DataSource.class)
@ConditionalOnProperty(name = "spring.datasource.type", havingValue = "com.zaxxer.hikari.HikariDataSource",
matchIfMissing = true)
static class Hikari {
@Bean
static HikariJdbcConnectionDetailsBeanPostProcessor jdbcConnectionDetailsHikariBeanPostProcessor(
ObjectProvider<JdbcConnectionDetails> connectionDetailsProvider) {
return new HikariJdbcConnectionDetailsBeanPostProcessor(connectionDetailsProvider);
}
@Bean
@ConfigurationProperties("spring.datasource.hikari")
HikariDataSource dataSource(DataSourceProperties properties, JdbcConnectionDetails connectionDetails,
Environment environment) {
String dataSourceClassName = environment.getProperty("spring.datasource.hikari.data-source-class-name");
HikariDataSource dataSource = createDataSource(connectionDetails, HikariDataSource.class,
properties.getClassLoader(), dataSourceClassName == null);
if (StringUtils.hasText(properties.getName())) {
dataSource.setPoolName(properties.getName());
}
return dataSource;
}
}
}
5、驱动加载原理:
JDBC 4.0+ 自动注册机制 (SPI)
驱动JAR中的声明
MySQL驱动JAR(mysq-connector-j-9.2.0.jar)包含文件:
META-INF/services/java.sql.Driver
内容:
com.mysql.cj.jdbc.Driver
DriverManager
自动加载首次调用
DriverManager.getConnection()
时,会自动扫描并注册所有META-INF/services
下的驱动实现类。DriverManager 的初始化逻辑:
代码位置:
java.sql.DriverManager#
getConnection关键方法:ensureDriversInitialized
()
public class DriverManager {
@CallerSensitive
public static Connection getConnection(String url,
java.util.Properties info) throws SQLException {
return (getConnection(url, info, Reflection.getCallerClass()));
}
/*
* Load the initial JDBC drivers by checking the System property
* jdbc.drivers and then use the {@code ServiceLoader} mechanism
*/
// 通过SPI 加载mysql driver
@SuppressWarnings("removal")
private static void ensureDriversInitialized() {
if (driversInitialized) {
return;
}
synchronized (lockForInitDrivers) {
if (driversInitialized) {
return;
}
String drivers;
try {
drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
public String run() {
return System.getProperty(JDBC_DRIVERS_PROPERTY);
}
});
} catch (Exception ex) {
drivers = null;
}
// If the driver is packaged as a Service Provider, load it.
// Get all the drivers through the classloader
// exposed as a java.sql.Driver.class service.
// ServiceLoader.load() replaces the sun.misc.Providers()
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
// SPI 加载Driver
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();
/* Load these drivers, so that they can be instantiated.
* It may be the case that the driver class may not be there
* i.e. there may be a packaged driver with the service class
* as implementation of java.sql.Driver but the actual class
* may be missing. In that case a java.util.ServiceConfigurationError
* will be thrown at runtime by the VM trying to locate
* and load the service.
*
* Adding a try catch block to catch those runtime errors
* if driver not available in classpath but it's
* packaged as service and that service is there in classpath.
*/
try {
while (driversIterator.hasNext()) {
driversIterator.next();
}
} catch (Throwable t) {
// Do nothing
}
return null;
}
});
println("DriverManager.initialize: jdbc.drivers = " + drivers);
if (drivers != null && !drivers.isEmpty()) {
String[] driversList = drivers.split(":");
println("number of Drivers:" + driversList.length);
for (String aDriver : driversList) {
try {
println("DriverManager.Initialize: loading " + aDriver);
Class.forName(aDriver, true,
ClassLoader.getSystemClassLoader());
} catch (Exception ex) {
println("DriverManager.Initialize: load failed: " + ex);
}
}
}
driversInitialized = true;
println("JDBC DriverManager initialized");
}
}
}
- 图解:
三、总结
1、自动配置:DataSourceAutoConfiguration
2、驱动加载: SPI(机制)