Druid 连接池最初就是为监控系统采集 jdbc 运行信息而生的,而实现强大的监控能力就是依靠 Filter 实现的,今天就看看 Druid 的 Filter 是如何运行的。
1、Filter 加载
Filter 添加监控可以通过 druidDataSource.addFilters("stat")方式进行添加,是 StatFilter 的别名,这个别名映射配置信息保存在 druid-xxx.jar!/META-INF/druid-filter.properties 中,其中自带的别名配置如下:
druid.filters.default=com.alibaba.druid.filter.stat.StatFilter
druid.filters.stat=com.alibaba.druid.filter.stat.StatFilter
druid.filters.mergeStat=com.alibaba.druid.filter.stat.MergeStatFilter
druid.filters.counter=com.alibaba.druid.filter.stat.StatFilter
druid.filters.encoding=com.alibaba.druid.filter.encoding.EncodingConvertFilter
druid.filters.log4j=com.alibaba.druid.filter.logging.Log4jFilter
druid.filters.log4j2=com.alibaba.druid.filter.logging.Log4j2Filter
druid.filters.slf4j=com.alibaba.druid.filter.logging.Slf4jLogFilter
druid.filters.commonlogging=com.alibaba.druid.filter.logging.CommonsLogFilter
druid.filters.commonLogging=com.alibaba.druid.filter.logging.CommonsLogFilter
druid.filters.wall=com.alibaba.druid.wall.WallFilter
druid.filters.config=com.alibaba.druid.filter.config.ConfigFilter
druid.filters.haRandomValidator=com.alibaba.druid.pool.ha.selector.RandomDataSourceValidateFilter
复制代码
还有一种方式是通过 SPI 的方式,在初始化 DruidDataSource 的时候,会调用 initFromSPIServiceLoader()该方法,该方法会使用 java 的 SPI 机制加载项目中的 Filter,需要注意的是只有被 AutoLoad 注解的才会被真正加载到 DruidDataSource 中。
ServiceLoader<Filter> autoFilterLoader = ServiceLoader.load(Filter.class);
for (Filter filter : autoFilterLoader) {
AutoLoad autoLoad = filter.getClass().getAnnotation(AutoLoad.class);
if (autoLoad != null && autoLoad.value()) {
filters.add(filter);
}
}
复制代码
2、Filter 接口
查看 Filter 的接口,里面包括 init,destroy,connection_connect,connection_createStatement 等等,覆盖了连接从重建到使用再到关闭的生命周期。
查看 Filter 的实现,发现只有 FilterAdapter 实现了 Filter 的接口,其余的各种 Filter 都是通过继承 FilterAdapter 实现的,官方的解释是:提供 JdbcFilter 的基本实现,使得实现一个 JdbcFilter 更容易。目前我还没有明白为什么要这么设计。
FilterAdapter 实现就是调用 FilterChain 的同名方法。而 FilterChain 只有要给 FilterChain 一个实现类 FilterChainImpl。里面有个关键方法:nextFilter()。该方法只有一行代码:
private Filter nextFilter() {
return getFilters()
.get(pos++);
}
复制代码
就是获取下一个 Filter。
3、执行过程
以初始化线程池时调用的 Filter 为例,调用 FilterChainImpl 的 dataSource_connect。
filterChain.dataSource_connect(this, maxWaitMillis);
复制代码
dataSource_connect 中的代码如下:
public DruidPooledConnection dataSource_connect(DruidDataSource dataSource, long maxWaitMillis) throws SQLException {
if (this.pos < filterSize) {
DruidPooledConnection conn = nextFilter().dataSource_getConnection(this, dataSource, maxWaitMillis);
return conn;
}
return dataSource.getConnectionDirect(maxWaitMillis);
}
复制代码
这里面的 nextFilter 刚刚说了就是获取下一个 Filter,所以这个方法是调用了下一个 Filter 的 dataSource_getConnection,下一个 Filter 也是这样,每一个 Filter 在调用自己的逻辑之前会先调用一下自己后面的 Filter,所以 Filter 调用是先添加的 Filter 中自己的逻辑会后被调用。
评论