上一章我们分析了,Druid实现监控功能的核心组件,JMX技术,这一章我们分析,Druid如何利用JMX实现指标监控的。
一.监控加载的入口
Druid通过何种方法把监控指标的相关代码运行起来的?答案就在下面:
public void init() throws SQLException {
//加载SPI中的Filter类,Filter类可以通过AutoLoad标签,来实现动态加载。默认不配置是主动加载
initFromSPIServiceLoader();
//通过jdbc连接的方式配置filter过滤器
if (this.jdbcUrl != null) {
this.jdbcUrl = this.jdbcUrl.trim();
//初始化jdbc:wrap-jdbc:driver=rawClassName:filters= , :name=: jmx=
initFromWrapDriverUrl();
}
下面这个方法是核心方法:先调用初始化方法
for (Filter filter : filters) {
filter.init(this);
}
}
Druid采用的是一种插件的方式,把监控相关的代码,插入到整个流程中去,这样做的好处,就是扩展性更强。并且它也提供了可以自定义实现的接口。所有的监控指标都存在StateFilter中。
二.开启监控方法
我们来思考一下,为啥监控方法采用filter来定义,其实细想,监控实现的功能--> 对各种指标的统计 --->如何统计各种指标--->数据在哪里产生就需要在哪里进行统计-->如果需要统计连接数,查询数,关闭数---->就需要在调用这些jdbc的原生方法时采用拦截的技术,其实就是通过拦截器在内部初始化代理类,对外暴露代理类,代理类包裹真实类,上层框架无感知,在调用这些方法时,就可以通过在代理类代理的方法内部,在调用真实方法之前或者之后做些统计的工作。
/**
先做初始化动作,就是把外部传进来的参数,初始化到内部对象中
**
/
public void init(DataSourceProxy dataSource) {
lock.lock();
try {
if (this.dbType == null) {
this.dbType = DbType.of(dataSource.getDbType());
}
//通过外部的properties给内部的属性初始化
configFromProperties(dataSource.getConnectProperties());
configFromProperties(System.getProperties());
} finally {
lock.unlock();
}
}
三.通过FilterChain创建filterChain中保存的过滤器的连接
public DruidPooledConnection getConnection(long maxWaitMillis) throws SQLException {
//先初始化
init();
//再创建连接
if (filters.size() > 0) {
FilterChainImpl filterChain = new FilterChainImpl(this);
return filterChain.dataSource_connect(this, maxWaitMillis);
} else {
return getConnectionDirect(maxWaitMillis);
}
}
FilterChainImpl是个核心的类,我们看看这个类中包含什么内容
public class FilterChainImpl implements FilterChain {
//当前的list中的下标
protected int pos = 0;
//里面包含了 List<filter> list 过滤器集合
private final DataSourceProxy dataSource;
//责任链中过滤器的数量
private final int filterSize;
}
其实就是经典的责任链模式中的成员变量,下面是整个初始化过程
public DruidPooledConnection dataSource_connect(DruidDataSource dataSource, long maxWaitMillis) throws SQLException {
//如果当前的下标比长度小,就先调用下一个过滤器
if (this.pos < filterSize) {
//label标签
DruidPooledConnection conn = nextFilter().dataSource_getConnection(this, dataSource, maxWaitMillis);
return conn;
}
return dataSource.getConnectionDirect(maxWaitMillis);
}
上面这几行代码就是整个初始化的关键,其调用顺序是这样的。
首先: 1号过滤器--->2---->3 此时所有的过滤器都停在label标签这一行。
调用3时,开启下一轮调用,因为3个最后一个过滤器,所以3调用的方法就会有返回值。3就会返回了, 此时又回到2,2返回又回到1,最后整个调用栈结束。
3-->2--->1。