druid源码研究之Filter
来源:互联网 发布:怎么复制淘宝宝贝视频 编辑:程序博客网 时间:2024/05/29 08:58
首先第一点,作者绝对看了tomcat的源代码,哈哈。druid里的私有字段
pos
,filterSize
和Tomcat中FilterChain
的实现类org.apache.catalina.core.ApplicationFilterChain
中的pos,n简直一模一样!作用完全一样,只是名字不一样而已,而且只是第二个不一样, druid的命名更能表述其意思.
Tomcat中的请参见
ApplicationFilterChain
中的internalDoFilter
方法, 或者本人的另外一篇博客.
1. 序言
- filter的意义就不用说了,该模式是优秀框架的必备基本要素之一.
- 之所以会研究这个,是因为最近2017-03-14试用了druid的密码加密功能.顺理成章地想要明白其原理(这里所谓的原理当然指的不是RSA算法)
DruidDataSource
的可配置属性都可以在DruidDataSourceFactory
类中找到(顶部的那些被public final static String
修饰的字段).
2. Filter
的初始化
- druid源码jar包下的
META-INF
文件夹下有一个名为druid-filter.properties
文件,其中配置了默认的filter. - 其中涉及到加密/解密的filter是
druid.filters.config=com.alibaba.druid.filter.config.ConfigFilter
- 而druid将
ConfigFilter
并入到自身则是使用了如下配置
<property name="filters" value="config" /> -- Spring dataSource.setFilters("config"); -- 编程方式
FilterManager
类内部有一个静态构造块,其中就会加载上面那个druid-filter.properties
文件,具体位置是第74行(druid-1.0.28.jar)DruidDataSource
类有一个init
方法,我们在spring中配置druid时,都会指定init-method='init'
. 而且DruidDataSource
也在其他地方,诸如getConnection()
方法里作了防御性编程, 也就是再调用一次init()
,防止程序员没有调用过该init()
, 而Filter的init()
就是在DruidDataSource
类的init
方法中被调用的.
3. Filter
接口
- 其中就约定了
init
,destroy
. 使得Filter
可以参与到druid生命周期里. - 不过对于此类需求, 这里我更推荐在Tomcat,Spring,Log4j2等知名框架内广泛使用的一种方式, 定义
Lifecycle
接口, 实现了该接口的类都能参与到生命周期中来. init
方法在DataSource
的druid实现类DruidDataSource
中被调用.
4. FilterAdapter
接口
- 实现了
Filter
接口, 其中对Filter
接口中所定义的方法的绝大部分实现都是直接调用方法参数chain(FilterChain
类型)的同名方法.这就大大减轻了其他Filter
实现类的重复性代码负担.
- 通过查看druid中
Filter
的类继承图也可以发现, 所有的Filter
实现类都是直接扩展自FilterAdapter
, 没有直接实现Filter
接口的类. 对于抽象类
FilterAdapter
,对其的注释是这样的 -提供JdbcFilter的基本实现,使得实现一个JdbcFilter更容易。
对JMX提供的支持这里不是我们所关注的, 就不再赘述.
5. FilterChain
接口
- 该接口的实现类在druid中只有一个,也就是
FilterChainImpl
- 任意挑选一个方法实现(这个真的是随意选的(。・`ω´・))
@Overridepublic void preparedStatement_setBlob(PreparedStatementProxy statement, int parameterIndex, InputStream inputStream) throws SQLException {// ----------- 可以看到本类的其他方法里的实现都是如下类似结构 // 推动链条往下执行 if (this.pos < filterSize) { // 这里的 nextFilter() 方法返回的是 FilterAdapter, // 而 FilterAdapter 对 Filter接口 的实现都是直接调度给了FilterChain的同名方法, 所以执行权回来了. // 所以对于自定义实现的Filter, 只需要覆盖自己感兴趣的方法. // 这样在 FilterAdapter 和 FilterChainImpl的相互的, 齿轮一样彼此推动之下将执行逻辑进行完毕. // 1. 逻辑从FilterChainImpl的实现开始, 在设置了Filter的前提下, 执行逻辑进入判断体内部, 也就是这个if语句内部; // 2. 然后在nextFilter()的返回值为FilterAdapter,及FilterAdapter 对 Filter接口 的实现都是直接调度给了FilterChain的同名方法(也就是本方法) // 3. 在这样的相互作用下, 下面这句的执行最终会导致某次进入本方法时,this.pos == this.filterSize, 从而执行本职工作, 再返回到这里. // 4. 最后由下面第三行返回. nextFilter().preparedStatement_setBlob(this, statement, parameterIndex, inputStream); return; } // 本方法的本职工作, 例如本方法就是为PreparedStatement设置Blob参数 statement.getRawObject().setBlob(parameterIndex, inputStream);}
- 其实仔细看下
FilterChain
接口里所定义的方法, 不出意外地都是围绕JDBC中的几个至关重要对象DataSource
,Connection
,Statement
,CallableStatement
,PreparedStatement
,ResultSet
. 所以虽然看似FilterChain
接口中定义了很多方法, 但是类别并不多.
6. 源码解析
- 这里我们以
DruidDataSource.getConnection()
为例子, 来看看Filter是如何参与执行逻辑的. - 很简单的几行代码,只看方法名我们就能大概猜到执行的逻辑.
6.1 DruidDataSource.getConnection()
方法
DruidDataSource
类的getConnection()
方法
public DruidPooledConnection getConnection(long maxWaitMillis) throws SQLException { // 防御性编程 init(); // 当程序员配置了filter之后,DruidDataSource内部的filters数组个数就不再为0 if (filters.size() > 0) { FilterChainImpl filterChain = new FilterChainImpl(this); return filterChain.dataSource_connect(this, maxWaitMillis); } else { return getConnectionDirect(maxWaitMillis); }}
- 这里首先要提到的是
FilterChainImpl
真正知道的是DataSourceProxy
,而不是直接的DataSource
,当然更不是直接的DruidDataSource
。对象和对象之间尽量不要直接接触,代之以中间层,为以后可能的变化引入缓冲带。 DruidDataSource
则是直接知道了FilterChainImpl
。
6.2 filterChain.dataSource_connect
方法
然后我们将目光集中到filterChain.dataSource_connect
中,里面的代码也很简单。
// FilterChainImpl对dataSource_connect的实现@Overridepublic DruidPooledConnection dataSource_connect(DruidDataSource dataSource, long maxWaitMillis) throws SQLException { // 迭代Filter if (this.pos < filterSize) { DruidPooledConnection conn = nextFilter().dataSource_getConnection(this, dataSource, maxWaitMillis); return conn; } // 获取Connection return dataSource.getConnectionDirect(maxWaitMillis);}
- 因为之前看过了Tomcat关于filterChain的逻辑,所以刚开始的时候百思不得其解,因为看了druid源码内部其他filter实现,没有对发现filterChain对filter.dataSource_getConnection的调用来将filter链执行下去。
- 直到看到了
Filter
的继承链,其唯一的继承抽象类FilterAdapter
将filter的dataSource_getConnection
的调用转嫁给了filterChain的dataSource_connect
调用.
6.3 FilterAdapter.dataSource_getConnection
方法
FilterAdapter
中的dataSource_getConnection
具体代码如下
public DruidPooledConnection dataSource_getConnection(FilterChain chain, DruidDataSource dataSource,long maxWaitMillis) throws SQLException { // 转嫁为chain的调用 return chain.dataSource_connect(dataSource, maxWaitMillis);}
- 真是相当凑巧,恰恰我选择来研究的这个方法不是转嫁为同名方法,所以也止住了我的匆匆扫描.
- 不过这也反过来帮我更深入一点得理解了tomcat源码。对比才能看到更多嘛。
6. FilterChain的执行逻辑模拟
正如在tomcat源代码中我对filter chain的模拟逻辑,这里也对druid里的filter chain的执行逻辑进行一次模拟.
6.1 准备
假如我们向druid中配置了两个filter ,即druid内部实现的Slf4jLogFilter
和StatFilter
,LogFilter在前,StaFilter在后.
于是FilterChainImpl中大概是这样的
1. 类级全局变量pos
(当前将要执行的filter在filter列表中的索引值), 我们这里是0
2. 以及类级全局变量filterSize
(将被执行的filter的总数量), 我们这里是2
6.2 开始执行
当我们调用DruidDataSource的getConnection()方法时,这也基本是我们平时调用的主方法了:
- 防御性地调用
init
方法,防止程序员没有对DruidDataSource
进行初始化(其中init
方法中就会调用每个filter的init
方法,完成filter的初始化). - 因为filters数组的大小为2,所以会实例化一个
FilterChainImpl
. - 然后就是调用filterChain.dataSource_connect
我们进入filterChain.dataSource_connect方法的实现中可以看到:
- 判断逻辑和tomcat中是一样的,也是(this.pos < filterSize)
- 不过tomcat中的filter里的doFilter是没有返回值的,而druid这里是有返回值的.
- pos的值发生改变只有一个地方,那就是私有方法
nextFilter()
中的pos++
,这一点上自我感觉比tomcat要强.逻辑被压缩到了一个位置. - 注意这里是
pos++
, 而不是++pos
. - 此时,pos为1,也就是下一个将要执行的filter在filter数组中的索引值,filterSize为2;
- 按照上面我们的假设,我们首先执行的是Slf4jLogFilter的dataSource_getConnection,其实就是调用的LogFilter的dataSource_getConnection,因为其并没有覆写LogFilter的实现.
- 而在LogFilter的dataSource_getConnection的实现中,首先就是调用了一次chain.dataSource_connect,
- 这一步就是将filter chain向前走一步.执行下一个filter.
- 这也就是说chain.dataSource_connect的目的就是为了执行完所有的filter,从而因为最终的this.pos = filterSize,从而执行dataSource.getConnectionDirect
- 这里就有一个细节了,在Filter传入的参数中是有DruidDataSource类型的dataSource的,而返回值是DruidPooledConnection,所以
- 我们可以在执行了某些操作之后,直接从dataSource中调用getConnectionDirect来获取一个DruidPooledConnection,给它返回回去,
- 注意druid最终就是调用dataSource.getConnectionDirect
- 注意这里不要调用dataSource.getConnection(),因为那样就递归了(猜测,未测试)
- 这样因为我们并没有调用chain.dataSource_connect,从而导致filter链条的中断.
- LogFilter在保证了filter链条的继续完整执行之后,最后拿到了DruidPooledConnection,
- 这里也有个注意点,如果偏后面的filter在返回DruidPooledConnection之前对其作了处理,那之前的filter拿到的就是被处理后的DruidPooledConnection
- 即 靠前的filter最终拿到的DruidPooledConnection,可能是会被靠后的filter处理过的.
- 这里和mybatis的plugin有点相像呢.
- LogFilter中执行的chain.dataSource_connect就会导致pos变成2,而filterSize依然为2;
- LogFilter中执行的chain.dataSource_connect导致pos变成2之后-nextFilter方法中,接着就是执行StatFilter,因为FilterChainImpl的nextFilter方法取到的就是它
- StatFilter的dataSource_getConnection方法中
- 也是首先保证链条继续往下执行, 即第一行就调用了一次chain.dataSource_connect.
- 这里千万别被迷惑到,这个chain.dataSource_connect的调用不必一定要在开始的位置,可以参考tomcat内部的实现.它们有的就是在自定义逻辑执行完毕之后将filter链往前推进的
- 当StatFilter的dataSource_getConnection方法也调用chain.dataSource_connect时
- 因为pos和filterSize都是2了,所以会直接执行FilterChainImpl的dataSource_connect方法中的最后一句代码,即dataSource.getConnectionDirect(maxWaitMillis)
- 这样StatFilter的dataSource_getConnection中就拿到了connect,就可以作一系列处理
- 最终将拿到的connect返回,其实也就是交给它的上一个filter,在这里就是LogFilter
- LogFilter拿到这个可能被StatFilter处理过的connect,继续自己的自定义处理逻辑
6.3 结论
- 如果filter会处理DruidPooledConnection,那么处理顺序和filter配置顺序相反.即靠后的filter会优先拿到Connect(前提是它们的获取位置一致,例如都是第一行就进行了调用)
- filter的执行顺序,就以logFilter和StatFilter为例的话,先配置的,其自定义逻辑后执行.因为它们都是最开始就执行了chain.dataSource_connect
- filter接口通过一个init方法,将自己的生命周期并入到框架中,来加入各自的自定义初始化逻辑.
- 值得借鉴,尤其是框架比较简单时,就没有必要参考tomcat搞那么复杂的生命周期继承链.
- ConfigFilter就是通过init来实现解密和远程配置文件的.
7. 几个方法
7.1 DruidDataSource.getConnection()
方法
DruidDataSource
类的getConnection()
方法
public DruidPooledConnection getConnection(long maxWaitMillis) throws SQLException { // 防御性编程 init(); // 当程序员配置了filter之后,DruidDataSource内部的filters数组个数就不再为0 if (filters.size() > 0) { FilterChainImpl filterChain = new FilterChainImpl(this); return filterChain.dataSource_connect(this, maxWaitMillis); } else { return getConnectionDirect(maxWaitMillis); }}
- 这里首先要提到的是
FilterChainImpl
真正知道的是DataSourceProxy
,而不是直接的DataSource
,当然更不是直接的DruidDataSource
。对象和对象之间尽量不要直接接触,代之以中间层,为以后可能的变化引入缓冲带。 DruidDataSource
则是直接知道了FilterChainImpl
。
7.2 filterChain.dataSource_connect
方法
然后我们将目光集中到filterChain.dataSource_connect
中,里面的代码也很简单。
// FilterChainImpl对dataSource_connect的实现@Overridepublic DruidPooledConnection dataSource_connect(DruidDataSource dataSource, long maxWaitMillis) throws SQLException { // 迭代Filter if (this.pos < filterSize) { DruidPooledConnection conn = nextFilter().dataSource_getConnection(this, dataSource, maxWaitMillis); return conn; } // 获取Connection return dataSource.getConnectionDirect(maxWaitMillis);}
- 因为之前看过了Tomcat关于filterChain的逻辑,所以刚开始的时候百思不得其解,因为看了druid源码内部其他filter实现,没有对发现filterChain对filter.dataSource_getConnection的调用来将filter链执行下去。
- 直到看到了
Filter
的继承链,其唯一的继承抽象类FilterAdapter
将filter的dataSource_getConnection
的调用转嫁给了filterChain的dataSource_connect
调用.
7.3 FilterAdapter.dataSource_getConnection
方法
FilterAdapter
中的dataSource_getConnection
具体代码如下
public DruidPooledConnection dataSource_getConnection(FilterChain chain, DruidDataSource dataSource,long maxWaitMillis) throws SQLException { // 转嫁为chain的调用 return chain.dataSource_connect(dataSource, maxWaitMillis);}
- 真是相当凑巧,恰恰我选择来研究的这个方法不是转嫁为同名方法,所以也止住了我的匆匆扫描.
- 不过这也反过来帮我更深入一点得理解了tomcat源码。对比才能看到更多嘛。
Links
- http://herman-liu76.iteye.com/blog/2308563 – 强力推荐一波, 作者比本人功力深厚多了, 而且其关于dubbo等的分析也可以看看.
- druid源码研究之Filter
- Hbase filter接口源码研究
- Druid源码
- druid Filter 配置WebStatFilter
- Druid filter过滤请求
- druid 的filter设计原理
- 6-druid源码分析之 Hadoop-index 过程
- druid源码解读
- Druid----DruidDataSource源码解析
- 【Hadoop源码研究】之Configuration
- elasticsearch源码研究之启动
- MyBatis源码研究之$和#
- Mybatis源码研究之DynamicContext
- Mybatis源码研究之XMLScriptBuilder
- Mybatis源码研究之BoundSql
- Mybatis源码研究之SqlSource
- Mybatis源码研究之Logger
- SpringBoot源码研究之Start
- Hadoop伪分布式搭建
- c 练习三
- java语言学习笔记(一)之初始java
- 强化学习(三)----- MDP的动态规划解法
- jQuery----文档加载事件
- druid源码研究之Filter
- 自定义回调控制OSG模型进行移动操作
- 2017秦皇岛ICPC
- Matlab学习笔记--数值计算
- c++基础之虚拟继承,虚函数virtual
- File
- JS入门
- 第八周项目三__对称矩阵压缩存储的实现与应用
- JVM开篇---Java内存区域与内存溢出异常