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. 序言

  1. filter的意义就不用说了,该模式是优秀框架的必备基本要素之一.
  2. 之所以会研究这个,是因为最近2017-03-14试用了druid的密码加密功能.顺理成章地想要明白其原理(这里所谓的原理当然指的不是RSA算法)
  3. DruidDataSource的可配置属性都可以在DruidDataSourceFactory类中找到(顶部的那些被public final static String修饰的字段).

2. Filter的初始化

  1. druid源码jar包下的META-INF文件夹下有一个名为druid-filter.properties文件,其中配置了默认的filter.
  2. 其中涉及到加密/解密的filter是druid.filters.config=com.alibaba.druid.filter.config.ConfigFilter
  3. 而druid将ConfigFilter并入到自身则是使用了如下配置
<property name="filters" value="config" />  -- Spring  dataSource.setFilters("config");  -- 编程方式
  1. FilterManager类内部有一个静态构造块,其中就会加载上面那个druid-filter.properties文件,具体位置是第74行(druid-1.0.28.jar)
  2. DruidDataSource类有一个init方法,我们在spring中配置druid时,都会指定 init-method='init'. 而且DruidDataSource也在其他地方,诸如getConnection()方法里作了防御性编程, 也就是再调用一次init(),防止程序员没有调用过该init(), 而Filter的init()就是在DruidDataSource类的init方法中被调用的.

3. Filter接口

  1. 其中就约定了 init, destroy. 使得Filter可以参与到druid生命周期里.
  2. 不过对于此类需求, 这里我更推荐在Tomcat,Spring,Log4j2等知名框架内广泛使用的一种方式, 定义Lifecycle接口, 实现了该接口的类都能参与到生命周期中来.
  3. init方法在 DataSource的druid实现类DruidDataSource中被调用.

4. FilterAdapter接口

  1. 实现了Filter接口, 其中对Filter接口中所定义的方法的绝大部分实现都是直接调用方法参数chain(FilterChain类型)的同名方法.这就大大减轻了其他Filter实现类的重复性代码负担.
    FilterAdapter对resultSetMetaData_isDefinitelyWritable方法的实现
  2. 通过查看druid中Filter的类继承图也可以发现, 所有的Filter实现类都是直接扩展自 FilterAdapter, 没有直接实现Filter接口的类.
    Filter的继承链
  3. 对于抽象类FilterAdapter,对其的注释是这样的 - 提供JdbcFilter的基本实现,使得实现一个JdbcFilter更容易。

  4. 对JMX提供的支持这里不是我们所关注的, 就不再赘述.

5. FilterChain接口

  1. 该接口的实现类在druid中只有一个,也就是FilterChainImpl
  2. 任意挑选一个方法实现(这个真的是随意选的(。・`ω´・))
@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);}
  1. 其实仔细看下FilterChain接口里所定义的方法, 不出意外地都是围绕JDBC中的几个至关重要对象DataSource, Connection, Statement, CallableStatement, PreparedStatement, ResultSet. 所以虽然看似FilterChain接口中定义了很多方法, 但是类别并不多.

6. 源码解析

  1. 这里我们以DruidDataSource.getConnection()为例子, 来看看Filter是如何参与执行逻辑的.
  2. 很简单的几行代码,只看方法名我们就能大概猜到执行的逻辑.
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);        }}
  1. 这里首先要提到的是FilterChainImpl真正知道的是DataSourceProxy,而不是直接的DataSource,当然更不是直接的DruidDataSource对象和对象之间尽量不要直接接触,代之以中间层,为以后可能的变化引入缓冲带
  2. 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);}
  1. 因为之前看过了Tomcat关于filterChain的逻辑,所以刚开始的时候百思不得其解,因为看了druid源码内部其他filter实现,没有对发现filterChain对filter.dataSource_getConnection的调用来将filter链执行下去。
  2. 直到看到了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);}
  1. 真是相当凑巧,恰恰我选择来研究的这个方法不是转嫁为同名方法,所以也止住了我的匆匆扫描.
  2. 不过这也反过来帮我更深入一点得理解了tomcat源码。对比才能看到更多嘛。

6. FilterChain的执行逻辑模拟

正如在tomcat源代码中我对filter chain的模拟逻辑,这里也对druid里的filter chain的执行逻辑进行一次模拟.

6.1 准备

假如我们向druid中配置了两个filter ,即druid内部实现的Slf4jLogFilterStatFilterLogFilter在前,StaFilter在后.

于是FilterChainImpl中大概是这样的
1. 类级全局变量pos(当前将要执行的filter在filter列表中的索引值), 我们这里是0
2. 以及类级全局变量filterSize(将被执行的filter的总数量), 我们这里是2

6.2 开始执行

当我们调用DruidDataSource的getConnection()方法时,这也基本是我们平时调用的主方法了:

  1. 防御性地调用init方法,防止程序员没有对DruidDataSource进行初始化(其中init方法中就会调用每个filter的init方法,完成filter的初始化).
  2. 因为filters数组的大小为2,所以会实例化一个FilterChainImpl.
  3. 然后就是调用filterChain.dataSource_connect
  4. 我们进入filterChain.dataSource_connect方法的实现中可以看到:

    1. 判断逻辑和tomcat中是一样的,也是(this.pos < filterSize)
    2. 不过tomcat中的filter里的doFilter是没有返回值的,而druid这里是有返回值的.
    3. pos的值发生改变只有一个地方,那就是私有方法nextFilter()中的pos++,这一点上自我感觉比tomcat要强.逻辑被压缩到了一个位置.
    4. 注意这里是pos++, 而不是++pos.
    5. 此时,pos为1,也就是下一个将要执行的filter在filter数组中的索引值,filterSize为2;
    6. 按照上面我们的假设,我们首先执行的是Slf4jLogFilter的dataSource_getConnection,其实就是调用的LogFilter的dataSource_getConnection,因为其并没有覆写LogFilter的实现.
    7. 而在LogFilter的dataSource_getConnection的实现中,首先就是调用了一次chain.dataSource_connect,
    8. 这一步就是将filter chain向前走一步.执行下一个filter.
    9. 这也就是说chain.dataSource_connect的目的就是为了执行完所有的filter,从而因为最终的this.pos = filterSize,从而执行dataSource.getConnectionDirect
    10. 这里就有一个细节了,在Filter传入的参数中是有DruidDataSource类型的dataSource的,而返回值是DruidPooledConnection,所以
    11. 我们可以在执行了某些操作之后,直接从dataSource中调用getConnectionDirect来获取一个DruidPooledConnection,给它返回回去,
      1. 注意druid最终就是调用dataSource.getConnectionDirect
      2. 注意这里不要调用dataSource.getConnection(),因为那样就递归了(猜测,未测试)
    12. 这样因为我们并没有调用chain.dataSource_connect,从而导致filter链条的中断.
    13. LogFilter在保证了filter链条的继续完整执行之后,最后拿到了DruidPooledConnection,
    14. 这里也有个注意点,如果偏后面的filter在返回DruidPooledConnection之前对其作了处理,那之前的filter拿到的就是被处理后的DruidPooledConnection
    15. 即 靠前的filter最终拿到的DruidPooledConnection,可能是会被靠后的filter处理过的.
    16. 这里和mybatis的plugin有点相像呢.
    17. LogFilter中执行的chain.dataSource_connect就会导致pos变成2,而filterSize依然为2;
    18. LogFilter中执行的chain.dataSource_connect导致pos变成2之后-nextFilter方法中,接着就是执行StatFilter,因为FilterChainImpl的nextFilter方法取到的就是它
    19. StatFilter的dataSource_getConnection方法中
    20. 也是首先保证链条继续往下执行, 即第一行就调用了一次chain.dataSource_connect.
    21. 这里千万别被迷惑到,这个chain.dataSource_connect的调用不必一定要在开始的位置,可以参考tomcat内部的实现.它们有的就是在自定义逻辑执行完毕之将filter链往前推进的
    22. 当StatFilter的dataSource_getConnection方法也调用chain.dataSource_connect时
    23. 因为pos和filterSize都是2了,所以会直接执行FilterChainImpl的dataSource_connect方法中的最后一句代码,即dataSource.getConnectionDirect(maxWaitMillis)
    24. 这样StatFilter的dataSource_getConnection中就拿到了connect,就可以作一系列处理
    25. 最终将拿到的connect返回,其实也就是交给它的上一个filter,在这里就是LogFilter
    26. LogFilter拿到这个可能被StatFilter处理过的connect,继续自己的自定义处理逻辑
6.3 结论
  1. 如果filter会处理DruidPooledConnection,那么处理顺序和filter配置顺序相反.即靠后的filter会优先拿到Connect(前提是它们的获取位置一致,例如都是第一行就进行了调用)
  2. filter的执行顺序,就以logFilter和StatFilter为例的话,先配置的,其自定义逻辑后执行.因为它们都是最开始就执行了chain.dataSource_connect
  3. filter接口通过一个init方法,将自己的生命周期并入到框架中,来加入各自的自定义初始化逻辑.
    1. 值得借鉴,尤其是框架比较简单时,就没有必要参考tomcat搞那么复杂的生命周期继承链.
    2. 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);        }}
  1. 这里首先要提到的是FilterChainImpl真正知道的是DataSourceProxy,而不是直接的DataSource,当然更不是直接的DruidDataSource对象和对象之间尽量不要直接接触,代之以中间层,为以后可能的变化引入缓冲带
  2. 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);}
  1. 因为之前看过了Tomcat关于filterChain的逻辑,所以刚开始的时候百思不得其解,因为看了druid源码内部其他filter实现,没有对发现filterChain对filter.dataSource_getConnection的调用来将filter链执行下去。
  2. 直到看到了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);}
  1. 真是相当凑巧,恰恰我选择来研究的这个方法不是转嫁为同名方法,所以也止住了我的匆匆扫描.
  2. 不过这也反过来帮我更深入一点得理解了tomcat源码。对比才能看到更多嘛。
  1. http://herman-liu76.iteye.com/blog/2308563 – 强力推荐一波, 作者比本人功力深厚多了, 而且其关于dubbo等的分析也可以看看.
原创粉丝点击