Druid源码分析

Druid源码分析


1. 入口类: DruidDataSource


1.1 结构


继承抽象类:DruidAbstractDataSource
接口方法:
DruidDataSourceMBean 
ManagedDataSource 
Referenceable
Closeable
Cloneable
ConnectionPoolDataSource
MBeanRegistration 


1.2 构造器构造
无参构造器
(1) 初始化非公平锁
new ReentrantLock(false)
根据重入锁 ReentrantLock获得两个Condition。
命名为empty 及 notEmpty


(2) 获得系统属性,并且从中取得与druid相关的属性(如果不为null)注入到本类中
configFromPropety(System.getProperties())
如果需要设置系统属性可以在VM options中设置,格式如下
-DconfigurePath=hello 
如果有空格则需要用引号
-DconfigurePath="hel lo"
取的方法为:
System.getProperty("configurePath")


2. 入口方法 getConnnection逆推
有两个方法 getPooledConnection() 和 getConnection()
其中前一个方法返回的连接池连接可以再调用getConnnection()方法返回Connnecton
PooledConnection表示数据源的物理连接,该连接在使用过后可以回收,不需要关闭,连接池管理提供挂钩的对象,
类似于Spring中 BeanDefinetion和BeanDefinetionHolder的关系
getConnection的时候实际上是从这个PooledConnection得到Connection


以下两个方法得到的连接都是封装好的Druid线程池连接。
        // 得到连接 DruidPooledConnection
        Connection connection =  druidDataSource.getConnection();
        // 得到连接 DruidPooledConnection
        PooledConnection pooledConnection = druidDataSource.getPooledConnection();


2.1 简单分析DruidPooledConnection
这个连接实现了Connnection和PooledConnection两个接口,相当于可以直接执行Connection的方法,也可以透过
getConnection返回该类封装的Connection对象。


2.2 方法分析,两个方法指向的方法实现都是
public DruidPooledConnection getConnection(long maxWaitMillis)
maxWaitMillis参数为最大等待时间


内嵌两个方法 init()初始化方法 和 调用过滤链方法。
如果有过滤链,采用过滤链来获得connection否则直接获得。


2.2.1 init()初始化方法分析
(1) 如果已经初始化直接返回  判断条件 inited = true ,默认为false;
(2) 取的锁lock,ReentrantLock lock = this.lock
调用 lock.lockInterruptibly()使得当前线程获得该锁。拿不到锁会休眠,第一次拿到锁,锁为1,如果当前线程已经有锁了,计数+1;
(3) 声明未初始化 boolean init = false,拿到锁后根据this.inited再判断一次是否初始化,已初始化返回
(4) 得到当前方法的堆栈信息,initStackTrace = Utils.toString(Thread.currentThread().getStackTrace());
委托的方法,遍历堆栈数组组装到StringBuilder中
(5) 赋值id值,委托给DruidDriver方法,原子类增加,再赋值给本类id return dataSourceIdSeed.incrementAndGet();
这里有一次向上转型,委托方法返回的是int,本类id为long类型
(6) 如果id大于1,说明不是一个数据源,需要设置一些参数
id从1的基础上每增加1,种子数就增加10w,
包括
连接对象种子 connectionIdSeed,
执行语句种子 statementIdSeed
结果集种子 resultSetIdSeed
事务种子 transactionIdSeed


(7) 判断jdbcUrl不为空,则对url地址进行去空格处理trim();
如果url地址是包装类(即以jdbc:wrap-jdbc:开头)则需要另外处理
根据url地址获得数据源代理类配置,设置对应的驱动类driverClass jdbcUrl 和name
将过滤器遍历添加到本类中。如果已经存在该过滤器就不再重复添加。

(8) 循环调用过滤器,遍历过滤器,执行init方法
比如对属性文件是否需要进行解密处理。


(9) 如果数据库类型dbType为空则,从url地址中判断dbType的类型
(10)如果dbtype是 mysql mariadb或者是aliyun_ads
默认缓存配置为false,如果连接属性当中个包含了该key,则设置为true
或者jdbcurl中cacheServerConfiguration信息存在(即不等于indexOf != -1)
如果为配置缓存,就在连接属性connectProperties配置cacheServerConfiguration为true
此处属性中有也覆盖为true了。
(11) 卫语句条件判断
最大活跃maxActive<=0抛入参异常 
maxActive < minIdle抛入参异常
初始大小getInitialSize() > maxActive抛入参异常
timeBetweenLogStatsMillis > 0 && useGlobalDataSourceStat 抛参数异常,timeBetweenLogStatsMillis不支持全局配置,需要默认为0的含义
maxEvictableIdleTimeMillis < minEvictableIdleTimeMillis 最大失效时间小于最小失效时间,抛SQL异常
(12) 对驱动类driverClass去空格处理
(13) initFromSPIServiceLoader 从SPI服务类中获得过滤链
取得jvm属性 String property = System.getProperty("druid.load.spifilter.skip");
不为空返回,为空则继续往下,取相应的filter增加到本类中
(14) 驱动为空,则从url地址中获得相应的driverClass
(15) 如果得到的是MockDriver的className名称,则返回MockDriver.instance 的Driver类型
如果不是,则采用JDBC工具返回,对应的class示例转换的Driver类型。
工具类先用传入的classLoader进行加载,没成功用当前线程的加载器,再用jvm默认的加载器加载
loadClass 和 Class.forName的区别
(16) 如果driver不为空,则取得该驱动class的名称
(17) 初始化检查
initCheck();
版本检查,如果是oracle数据库,driver.getMajorVersion()<10 抛异常,版本问题
分别对DB2 ORACLE进行查询检查验证,mysql不需要检查,设置isMySQL为true

initExceptionSorter
初始化ExceptionSorter引用,异常分类,根据不同的数据库进行分类

initValidConnectionChecker()
初始化有效连接验证引用,根据数据库类型分类

validationQueryCheck();
校验查询
(18) 如果是全局设置isUseGlobalDataSourceStat
对 JdbcDataSourceStat dataSourceStat 进行初始化设置,如果是全局设置根据全局设置初始化该对象
否则根据name url this.dbType初始化该对象
dataSourceStat.setResetStatEnable(this.resetStatEnable); 设置复位参数,默认true
(19)初始化连接数量
根据maxActive参数初始化连接钩子对象DruidConnectionHolder,数组
三个数组,按最大数组来初始化
总连接数数组 volatile DruidConnectionHolder[] connections  
存活的连接数数组 DruidConnectionHolder[] keepAliveConnections; 
失效的连接数数组 DruidConnectionHolder[] evictConnections;


(20) ScheduledExecutorService createScheduler !=null
创建线程池不为空,根据 初始数量initialSize,循环提交创建CreateConnectionTask连接的线程
初始化为true
由于需要对createCount进行验证和修改,所以需要先取锁,lock.lock 最后finally lock.unlock
需要有创建线程等待才能创建连接,根据变量Throwable createError != null && int poolingCount == 0
对设置的数据进行验证,否则无法创建,直接return
(21)每一个线程都开始创建一个物理连接 ,委托给以下方法
PhysicalConnectionInfo createPhysicalConnection()
创建物理连接:
a. 获得url getUrl()
b. 获得连接属性 getConnectProperties()
c. 获得username getUserCallback().getName()/ getUsername()
d. 获得password getPassword() / getPasswordCallback password回调
e. 将连接属性设置为新的properties对象中,并且把username和password均放到其中
f. 获得连接,在获得连接的前后记录下纳秒时间
h. 定义全局vars和初始var 用HashMap


(22) 根据新物理连接属性创建物理连接 
createPhysicalConnection(url, physicalConnectProperties)
Connection createPhysicalConnection(String url, Properties info)
如果没有代理直接用driver.connect(url,info)创建
有代理用conn = new FilterChainImpl(this).connection_connect(info);拦截器创建
创建数量+1,记录创建时间戳
createCount.incrementAndGet();
没创建成功抛异常


(23) 对创建的连接根据全局设置进行设置
如果不是自动提交。设置为自动提交
如果默认只读有值就设置为相应的值
如果事务隔离设置有值,就设置为相应的值
如果默认目录getDefaultCatalog()不为空,就设置为相应的信息
如果初始化的sql语句为空就返回,不为空就继续根据Connection创建执行语句
stmt = conn.createStatement();
for (String sql : initSqls) {
if (sql == null) {
continue;
}
stmt.execute(sql);
}
循环执行相应的sql语句
如果是mysql数据库或者aliyun数据库,就执行查询语句
将值循环取出放在初始值当中,finally中关闭结果集
rs = stmt.executeQuery("show variables");
while (rs.next()) {
String name = rs.getString(1);
Object value = rs.getObject(2);
variables.put(name, value);
   }
因为一般结果集合是随着connection关闭的
,但是这里connection是不关闭的,所以需要单独的关闭rs


再一次执行 show global variables 获得全局的属性值
(24)校验connection是否可用,采用一个查询语句,查询,记录校验的时间
设置创建的错误,采用类型 Throwable,设置为null
(25)异常链,catch,然后在语句块中 setCreateError(ex);
JdbcUtils.close(conn);
throw ex;
关闭连接并且抛出捕捉到的异常
(26) 根据得到的 new PhysicalConnectionInfo(conn,
 connectStartNanos, connectedNanos, initedNanos, 
 validatedNanos, variables, globalVariables);
 返回一个物理连接。
(27) 设置失败标志为false,有异常的设置为true;


(28) 如果不是异步初始化,便直接采用创建物理连接,在根据物理连接构造连接Holder,再放入数组中
(29) 创建线程
a.创建日志守护线程,获得相应的参数值,打印
b.创建创建线程守护线程,
c.创建销毁线程。根据holder中存储的时间戳和现在比较,如果超过了时间
将对象放置在失效连接数组中,保持存活的在keepalive数组中
d. 对失效数组中的connection进行关闭操作,填充数组为null
e. 对保持有效的连接,验证查询有效,设置holder的最后一次活跃时间戳为当前,将holder增加到connections连接数组中
然后关闭该连接,填充数组为null。
f. initedLatch.await();初始化线程阻塞,创建和销毁线程
(30) 注册MBEAN
(31) 为了保持活跃,最小数量的连接,通过线程池异步循环执行。
(32) 最后没有异常,将初始化inited设置为true,并且释放锁。打印日志。
-------以上init() 方法初始化就结束了------


以下获取连接
(1) 对象已经初始化完成
有调用链的情况下,调用链通过代理来完成获取。
没有的情况下直接获取
如果是发现poolCount为0 则通知线程自动创建新的connection。
通知的方式采用 condition.signal()的方式通知。


(2) 直接获取的逻辑
类似。








   

你可能感兴趣的:(源码,druid,连接池)