一、配置环境
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-nameif (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=utf8if (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 {@Beanstatic 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 {@CallerSensitivepublic 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 加载DriverServiceLoader<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(机制)