一、SPI介绍
SPI的全名为Service Provider Interface,即服务提供程序接口,是Java提供的一套用来被第三方实现或者扩展的API,是JDK内置的一种服务提供发现机制,可以用来启用框架扩展和替换组件。优点是:①实现三方服务可插拔,②便于提供方实现、变更、扩展服务 ③简化用户配置,添加提供方依赖即可使用默认配置服务,提高开发效率。 Java的SPI约定服务的提供方需要在资源路径下的META-INF/services文件夹下面以服务的接口为文件名提供服务接口实现类,自动加载文件里所定义的类
二、Mysql的数据库驱动服务实现
1. 驱动版本:5.1.29及以下的版本
2.驱动版本:5.1.30到6.0.2之间的版本
3.驱动版本6.0.2及以上的版本
三、数据库接口实现源码
1.Java提供了数据库驱动服务接口:java.sql.Driver
2.Mysql实现的注册驱动,根据上面的版本查看,不同版本之间实现不同:
6.0.2版本之前核心注册驱动为:com.mysql.jdbc.Driver 6.0.2版本及之后注册驱动统一为:com.mysql.cj.jdbc.Driver
3.驱动管理和注册驱动
Java的数据库管理:java.sql.DriverManager 驱动管理器DriverManager在初始化时加载了SPI驱动服务:
static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
加载数据库驱动:
private static void loadInitialDrivers() {
String drivers;
try {
drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
public String run() {
return System.getProperty("jdbc.drivers");
}
});
} catch (Exception ex) {
drivers = null;
}
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();
try{
while(driversIterator.hasNext()) {
driversIterator.next();
}
} catch(Throwable t) {
}
return null;
}
});
println("DriverManager.initialize: jdbc.drivers = " + drivers);
if (drivers == null || drivers.equals("")) {
return;
}
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);
}
}
}
ServiceLoader中java.util.ServiceLoader#reload创建迭代器:java.util.ServiceLoader.LazyIterator
迭代器查找资源路径下面的SPI文件:
private boolean hasNextService() {
if (nextName != null) {
return true;
}
if (configs == null) {
try {
String fullName = PREFIX + service.getName();
if (loader == null)
configs = ClassLoader.getSystemResources(fullName);
else
configs = loader.getResources(fullName);
} catch (IOException x) {
fail(service, "Error locating configuration files", x);
}
}
while ((pending == null) || !pending.hasNext()) {
if (!configs.hasMoreElements()) {
return false;
}
pending = parse(service, configs.nextElement());
}
nextName = pending.next();
return true;
}
private S nextService() {
if (!hasNextService())
throw new NoSuchElementException();
String cn = nextName;
nextName = null;
Class<?> c = null;
try {
c = Class.forName(cn, false, loader);
} catch (ClassNotFoundException x) {
fail(service,
"Provider " + cn + " not found");
}
if (!service.isAssignableFrom(c)) {
fail(service,
"Provider " + cn + " not a subtype");
}
try {
S p = service.cast(c.newInstance());
providers.put(cn, p);
return p;
} catch (Throwable x) {
fail(service,
"Provider " + cn + " could not be instantiated",
x);
}
throw new Error();
}
DriverManager使用了CopyOnWriteArrayList集合存储了驱动实例集合:
private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();
CopyOnWriteArrayList:
final transient ReentrantLock lock = new ReentrantLock();
private transient volatile Object[] array;
Mysql在初始化时会注册自己的驱动到驱动管理器的集合中: java.sql.DriverManager#registerDriver(java.sql.Driver, java.sql.DriverAction): 驱动管理DriverManager提供数据库连接的创建: public java.sql.DriverManager#getConnection(String url) public java.sql.DriverManager#getConnection(String url,String user, String password) public java.sql.DriverManager#getConnection(String url,java.util.Properties info) private java.sql.DriverManager#getConnection(String url, java.util.Properties info, Class<?> caller)
for(DriverInfo aDriver : registeredDrivers) {
if(isDriverAllowed(aDriver.driver, callerCL)) {
try {
println(" trying " + aDriver.driver.getClass().getName());
Connection con = aDriver.driver.connect(url, info);
if (con != null) {
println("getConnection returning " + aDriver.driver.getClass().getName());
return (con);
}
} catch (SQLException ex) {
if (reason == null) {
reason = ex;
}
}
} else {
println(" skipping: " + aDriver.getClass().getName());
}
}
4.旧版驱动创建数据库连接-NonRegisteringDriver
public java.sql.Connection connect(String url, Properties info)
throws SQLException {
if (url != null) {
if (StringUtils.startsWithIgnoreCase(url, LOADBALANCE_URL_PREFIX)) {
return connectLoadBalanced(url, info);
} else if (StringUtils.startsWithIgnoreCase(url,
REPLICATION_URL_PREFIX)) {
return connectReplicationConnection(url, info);
}
}
Properties props = null;
if ((props = parseURL(url, info)) == null) {
return null;
}
if (!"1".equals(props.getProperty(NUM_HOSTS_PROPERTY_KEY))) {
return connectFailover(url, info);
}
try {
Connection newConn = com.mysql.jdbc.ConnectionImpl.getInstance(
host(props), port(props), props, database(props), url);
return newConn;
} catch (SQLException sqlEx) {
throw sqlEx;
} catch (Exception ex) {
SQLException sqlEx = SQLError.createSQLException(Messages
.getString("NonRegisteringDriver.17")
+ ex.toString()
+ Messages.getString("NonRegisteringDriver.18"),
SQLError.SQL_STATE_UNABLE_TO_CONNECT_TO_DATASOURCE, null);
sqlEx.initCause(ex);
throw sqlEx;
}
}
5.新版驱动基于不同的连接类型创建数据库连接-NonRegisteringDriver
相对于旧版,做了很多优化:
public Connection connect(String url, Properties info) throws SQLException {
try {
try {
if (!ConnectionUrl.acceptsUrl(url)) {
return null;
} else {
ConnectionUrl conStr = ConnectionUrl.getConnectionUrlInstance(url, info);
switch(conStr.getType()) {
case SINGLE_CONNECTION:
return ConnectionImpl.getInstance(conStr.getMainHost());
case FAILOVER_CONNECTION:
case FAILOVER_DNS_SRV_CONNECTION:
return FailoverConnectionProxy.createProxyInstance(conStr);
case LOADBALANCE_CONNECTION:
case LOADBALANCE_DNS_SRV_CONNECTION:
return LoadBalancedConnectionProxy.createProxyInstance(conStr);
case REPLICATION_CONNECTION:
case REPLICATION_DNS_SRV_CONNECTION:
return ReplicationConnectionProxy.createProxyInstance(conStr);
default:
return null;
}
}
} catch (UnsupportedConnectionStringException var5) {
return null;
} catch (CJException var6) {
throw (UnableToConnectException)ExceptionFactory.createException(UnableToConnectException.class, Messages.getString("NonRegisteringDriver.17", new Object[]{var6.toString()}), var6);
}
} catch (CJException var7) {
throw SQLExceptionsMapping.translateException(var7);
}
}
6.双重校验和可重入读写锁的使用实例
com.mysql.cj.conf.ConnectionUrl#getConnectionUrlInstance
public static ConnectionUrl getConnectionUrlInstance(String connString, Properties info) {
if (connString == null) {
throw ExceptionFactory.createException(WrongArgumentException.class, Messages.getString("ConnectionString.0"));
}
String connStringCacheKey = buildConnectionStringCacheKey(connString, info);
ConnectionUrl connectionUrl;
rwLock.readLock().lock();
connectionUrl = connectionUrlCache.get(connStringCacheKey);
if (connectionUrl == null) {
rwLock.readLock().unlock();
rwLock.writeLock().lock();
try {
connectionUrl = connectionUrlCache.get(connStringCacheKey);
if (connectionUrl == null) {
ConnectionUrlParser connStrParser = ConnectionUrlParser.parseConnectionString(connString);
connectionUrl = Type.getConnectionUrlInstance(connStrParser, info);
connectionUrlCache.put(connStringCacheKey, connectionUrl);
}
rwLock.readLock().lock();
} finally {
rwLock.writeLock().unlock();
}
}
rwLock.readLock().unlock();
return connectionUrl;
}
|