今天在自己的democode中实现了 init driver class的方法
public class DataSource {
protected Driver driver;
protected volatile String driverClass;
protected volatile ClassLoader driverClassLoader;
protected volatile String jdbcUrl ;
public void resolveDriver( ) throws SQLException {
if (this.driver == null) {
if (this.driverClass == null || this.driverClass.isEmpty()) {
this.driverClass = JdbcUtils.getDriverClassName(/*this.*/jdbcUrl);
}
//存在扩展任何driverclass-hive clickhouse
if (jdbcUrl == null && (driverClass == null || driverClass.length() == 0)) {
throw new SQLException("url not set");
}
driver = JdbcUtils.createDriver(driverClassLoader, driverClass);
} else {
if (this.driverClass == null) {
this.driverClass = driver.getClass().getName();
}
}
System.out.println("driverClass"+driverClass.toString());
}
}
简单的实现了resolveDriver这个方法,在之前略略看过的过程中,了解到这个方法是通过url来解析获得driver的类别
public static String getDriverClassName(String rawUrl) throws SQLException {
if (rawUrl == null) {
return null;
}
if (rawUrl.startsWith("jdbc:derby:")) {
return "org.apache.derby.jdbc.EmbeddedDriver";
} else if (rawUrl.startsWith("jdbc:mysql:")) {
if (mysql_driver_version_6 == null) {
mysql_driver_version_6 = loadClass("com.mysql.cj.jdbc.Driver") != null;
}
if (mysql_driver_version_6) {
return MYSQL_DRIVER_6;
} else {
return MYSQL_DRIVER;
}
} else if (rawUrl.startsWith("jdbc:log4jdbc:")) {
return LOG4JDBC_DRIVER;
} else if (rawUrl.startsWith("jdbc:mariadb:")) {
return MARIADB_DRIVER;
} else if (rawUrl.startsWith("jdbc:oracle:") //
|| rawUrl.startsWith("JDBC:oracle:")) {
return ORACLE_DRIVER;
} else if (rawUrl.startsWith("jdbc:microsoft:")) {
return "com.microsoft.jdbc.sqlserver.SQLServerDriver";
}
如上述代码示例,方法通过url的格式获得了driver的具体方法,
public static Driver createDriver(ClassLoader classLoader, String driverClassName) throws SQLException {
Class<?> clazz = null;
if (classLoader != null) {
try {
clazz = classLoader.loadClass(driverClassName);
} catch (ClassNotFoundException e) {
// skip
}
}
if (clazz == null) {
try {
ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
if (contextLoader != null) {
clazz = contextLoader.loadClass(driverClassName);
}
} catch (ClassNotFoundException e) {
// skip
}
}
if (clazz == null) {
try {
clazz = Class.forName(driverClassName);
} catch (ClassNotFoundException e) {
throw new SQLException(e.getMessage(), e);
}
}
try {
return (Driver) clazz.newInstance();
} catch (IllegalAccessException e) {
throw new SQLException(e.getMessage(), e);
} catch (InstantiationException e) {
throw new SQLException(e.getMessage(), e);
}
}
最后通过获得的类名称调用类加载器classloader返回具体的jdbc方法。
这样就完成了一个driver class的初始化
在这个过程中我们在constant中可以看到一个很有意思的类型枚举DbType
public enum DbType {
other (1 << 0),
jtds (1 << 1),
hsql (1 << 2),
db2 (1 << 3),
postgresql (1 << 4),
sqlserver (1 << 5),
oracle (1 << 6),
mysql (1 << 7),
mariadb (1 << 8),
derby (1 << 9),
hive (1 << 10),
h2 (1 << 11),
dm (1 << 12), // dm.jdbc.driver.DmDriver
kingbase (1 << 13),
gbase (1 << 14),
oceanbase (1 << 15),
informix (1 << 16),
odps (1 << 17),
teradata (1 << 18),
phoenix (1 << 19),
edb (1 << 20),
kylin (1 << 21), // org.apache.kylin.jdbc.Driver
sqlite (1 << 22),
ads (1 << 23),
presto (1 << 24),
elastic_search (1 << 25), // com.alibaba.xdriver.elastic.jdbc.ElasticDriver
hbase (1 << 26),
drds (1 << 27),
...
;
public final long mask;
public final long hashCode64;
private DbType(long mask) {
this.mask = mask;
this.hashCode64 = FnvHash.hashCode64(name());
}
public static long of(DbType... types) {
long value = 0;
for (DbType type : types) {
value |= type.mask;
}
return value;
}
public static DbType of(String name) {
if (name == null || name.isEmpty()) {
return null;
}
if ("aliyun_ads".equalsIgnoreCase(name)) {
return ads;
}
try {
return valueOf(name);
} catch (Exception e) {
return null;
}
}
public final boolean equals(String other) {
return this == of(other);
}
在这个枚举中引用了 FNV哈希算法 FnvHash.hashCode64(name(), FNV哈希算法是一种高离散性的哈希算法,特别适用于哈希非常相似的字符串,例如:URL,IP,主机名,文件名等。
if (this.dbTypeName == null || this.dbTypeName.length() == 0) {
this.dbTypeName = JdbcUtils.getDbType(jdbcUrl, null);
}
//获取DbType的方法
public static String getDbType(String rawUrl, String driverClassName) {
DbType dbType = getDbTypeRaw(rawUrl, driverClassName);
if (dbType == null) {
return null;
}
return dbType.name();
}
这里的话会根据url获取dbtype ,一串经过fnvhash的字符串比如mysql就转换成为 (1 << 7)
而这个的用处是什么呢?
我们在写枚举参数时,可以用 | 运算符传入多个枚举值,比如:oracle | mysql。之所以可以这样写,是因为在定义枚举时提供了位运算。
将一个数字转换成二进制数,枚举中使用的是1,所以二进制数就是 0000 0001 ,将其转换成十进制数就是 1 * 2^0 + 0 * 2 ^ 1 + 0 * 2 ^ 2 + …… ,实际上就是 2^0 。
而位运算 1 << 7,就是 1000 0000 ,将1向左移动7位,其结果就是 0 * 2^0 + 0 * 2^1 +……+ 0 * 2 ^ 7,结果是240 。同理,其它几个结果也是这么运算的
这里的最后结果就是通过位运算的枚举把长段的url,直接转换为了一个数字,以降低整个运行过程中所用到的内存和加快运算的速度,
DbType dbType = DbType.of(this.dbTypeName);
if (dbType == DbType.mysql
|| dbType == DbType.mariadb
|| dbType == DbType.oceanbase
|| dbType == DbType.ads) {
boolean cacheServerConfigurationSet = false;
if (this.connectProperties.containsKey("cacheServerConfiguration")) {
cacheServerConfigurationSet = true;
} else if (this.jdbcUrl.indexOf("cacheServerConfiguration") != -1) {
cacheServerConfigurationSet = true;
}
if (cacheServerConfigurationSet) {
this.connectProperties.put("cacheServerConfiguration", "true");
}
}
这一段就是位运算枚举转换以后的一段应用:利用位运算枚举版本的数据库类型dbType,判断是否要初始化cacheServerConfigurationSet这一个配置。
再往下看一个非常重要的方法
while (poolingCount < initialSize) {
try {
PhysicalConnectionInfo pyConnectInfo = createPhysicalConnection();
DruidConnectionHolder holder = new DruidConnectionHolder(this, pyConnectInfo);
connections[poolingCount++] = holder;
} catch (SQLException ex) {
LOG.error("init datasource error, url: " + this.getUrl(), ex);
if (initExceptionThrow) {
connectError = ex;
break;
} else {
Thread.sleep(3000);
}
}
首先统计了整个连接池中的资源poolingCount ,poolingCount是一个非常重要的计数器 可以看到获得这个数字的方法都是加锁的。
@Override
public int getPoolingCount() {
lock.lock();
try {
return poolingCount;
} finally {
lock.unlock();
}
}
如果获得的poolingCount最后是小于设置的最大数量这里就开始获取物理连接了。
从上可以看出,druid获取数据库连接首先也是要获取到 driver 然后再调用它的 connect 方法。
上面代码的主要逻辑也还是先实例化 driver, 然后通过 driver 获取一个数据库连接,这种方式和jdbc是一致的,只不过driud帮我们做了这件事情,我们不用再自己做了而已。并且DruidDataSource 在获取到连接之后,会把连接存在池中,以达到我们每次请求都可以直接从池中分配连接,而不是自己再去重新初始化的一个作用。
接下来是一个死循环的创建:
初始化创建和销毁线程,即调起这个线程进行各自工作,里面通过锁控制
protected void createAndStartCreatorThread() {
if (createScheduler == null) {
String threadName = "Druid-ConnectionPool-Create-" + System.identityHashCode(this);
createConnectionThread = new CreateConnectionThread(threadName);
createConnectionThread.start();
return;
}
initedLatch.countDown();
}
createAndStartCreatorThread里面调用了CreateConnectionThread
在这个thread中还同时调用了关闭线程
首先线程启动的时候 调用initedLatch.countDown(),这是一个
CountDownLatch来控制整个线程池初始化的进程
public class CreateConnectionThread extends Thread {
public CreateConnectionThread(String name){
super(name);
this.setDaemon(true);
}
public void run() {
initedLatch.countDown();
long discardCount = DruidDataSource.this.discardCount;
boolean discardChanged = discardCount - lastDiscardCount > 0;
接下来是连接池创建的条件emptyWait,如果有异常,或者诸如 poolingCount == 0并且(连接数为空了)并且discardChanged 并没有改变 等等等。程序就会在empty条件上等待创建,就是等emptyWait这个条件成立再运行,并且在创建的时候还会再次判断不超过maxActive数量的连接
boolean emptyWait = true;
if (createError != null
&& poolingCount == 0
&& !discardChanged) {
emptyWait = false;
}
if (emptyWait
&& asyncInit && createCount < initialSize) {
emptyWait = false;
}
if (emptyWait) {
// 必须存在线程等待,才创建连接
if (poolingCount >= notEmptyWaitThreadCount //
&& (!(keepAlive && activeCount + poolingCount < minIdle))
&& !isFailContinuous()
) {
empty.await();
}
// 防止创建超过maxActive数量的连接
if (activeCount + poolingCount >= maxActive) {
empty.await();
continue;
}
}
在线程创建的最后是一个死循环: 如果没创建连接,继续,如果连接关闭成功,跳出循环 直觉上这可能会导致一些bug...
if (connection == null) {
continue;
}
boolean result = put(connection);
if (!result) {
JdbcUtils.close(connection.getPhysicalConnection());
LOG.info("put physical connection to pool failed.");
}
errorCount = 0; // reset errorCount
if (closing || closed) {
break;
}
创建线程的过程就讲到这里,明天我们再看一下销毁线程的部分代码。