自定義Druid的攔截器

Druid是一個JDBC組件,它包括三部分: 

  1. DruidDriver 代理Driver,可以提供基於Filter-Chain模式的插件體系。 
  2. DruidDataSource 高效可管理的數據庫鏈接池。 
  3. SQLParser 

Druid能夠作什麼? 

1) 能夠監控數據庫訪問性能,Druid內置提供了一個功能強大的StatFilter插件,可以詳細統計SQL的執行性能,這對於線上分析數據庫訪問性能有幫助。 node

2) 替換DBCPC3P0。Druid提供了一個高效、功能強大、可擴展性好的數據庫鏈接池。 git

3) 數據庫密碼加密。直接把數據庫密碼寫在配置文件中,這是很差的行爲,容易致使安全問題。DruidDruiver和DruidDataSource都支持PasswordCallback。 github

4) SQL執行日誌,Druid提供了不一樣的LogFilter,可以支持Common-LoggingLog4j和JdkLog,你能夠按須要選擇相應的LogFilter,監控你應用的數據庫訪問狀況。 sql

擴展JDBC,若是你要對JDBC層有編程的需求,能夠經過Druid提供的Filter-Chain機制,很方便編寫JDBC層的擴展插件。 數據庫

-------------------------------------分割線--------------------------------------------編程

怎麼使用Druid提供的Filter-Chain機制,方便編寫JDBC層的擴展插件呢?

這纔是本篇文章重點要描述的。安全

https://github.com/alibaba/druid/wiki/%E9%A6%96%E9%A1%B5 druid官方里面好像沒找到。ide

(代碼裏面的註釋真的少得可憐。。。)函數

不過用過druid的知道里面內置了一些filter性能

例如在配置

<property name="filters" value="stat,log4j" />

附內置filter的別名

Filter類名 別名
default com.alibaba.druid.filter.stat.StatFilter
stat com.alibaba.druid.filter.stat.StatFilter
mergeStat com.alibaba.druid.filter.stat.MergeStatFilter
encoding com.alibaba.druid.filter.encoding.EncodingConvertFilter
log4j com.alibaba.druid.filter.logging.Log4jFilter
log4j2 com.alibaba.druid.filter.logging.Log4j2Filter
slf4j com.alibaba.druid.filter.logging.Slf4jLogFilter
commonlogging com.alibaba.druid.filter.logging.CommonsLogFilter

這些Filter都實現了com.alibaba.druid.filter.Filter 這個接口,裏面的函數太多。。

從哪裏開始?

其實最簡單的方法就是仿照已有的Filter來寫,例如Log4jFilter,

Log4jFilter 繼承 LogFilter,從上面看出,Druid日誌插件還支持Slf4jLog,CommonsLog。這裏面其實就是根據不一樣狀況打印不一樣的日誌。

LogFilter又繼承於FilterEventAdapter這個類,這個類好像很熟悉,貌似Spring裏面常常會有xxxAdapter來幫咱們對一些接口實現一些默認的方法,而咱們只須要繼承這個類, 重寫裏面須要重寫方法就能夠了。

裏面最重要的方法就是

@Override
public boolean preparedStatement_execute(FilterChain chain, PreparedStatementProxy statement) throws SQLException {
    try {
        //抽象方法
        statementExecuteBefore(statement, statement.getSql());

        boolean firstResult = chain.preparedStatement_execute(statement);

        //抽象方法,後面咱們會重寫這個方法
        this.statementExecuteAfter(statement, statement.getSql(), firstResult);

        return firstResult;

    } catch (SQLException error) {
        //抽象方法
        statement_executeErrorAfter(statement, statement.getSql(), error);
        throw error;
    } catch (RuntimeException error) {
        //抽象方法
        statement_executeErrorAfter(statement, statement.getSql(), error);
        throw error;
    } catch (Error error) {
        //抽象方法
        statement_executeErrorAfter(statement, statement.getSql(), error);
        throw error;
    }

}

好吧,這個方法就是實行FilterChain的preparedStatement_execute方法。這裏就是filter的執行鏈上的一個node把。

依葫蘆畫瓢

1. 建立一個Filter

/**
 * Created by hzlizhou on 2017/2/5.
 */
public class TestFilter extends FilterEventAdapter{
    @Override
    protected void statementExecuteAfter(StatementProxy statement, String sql, boolean result) {
        System.out.println("haha");
        super.statementExecuteAfter(statement, sql, result);
    }
}

咱們本身寫一個TestFilter, 繼承FilterEventAdapter,重寫statementExecuteAfter方法(在SQL語句執行成功之後會執行)

2. 加入到執行鏈(ChainFilter)

仔細看DruidDataSource,他的父類,裏面有個

protected List<Filter> filters  = new CopyOnWriteArrayList<Filter>();

<property name="filters" value="stat,log4j" /> DataSource配置的時候就是調用這個set方法

public void setFilters(String filters) throws SQLException {
    if (filters != null && filters.startsWith("!")) {
        filters = filters.substring(1);
        this.clearFilters();
    }
    this.addFilters(filters);
}

還有一個set方法是直接加入filters

public void setProxyFilters(List<Filter> filters) {
    if (filters != null) {
        this.filters.addAll(filters);
    }
}

所以,咱們在datasource配置中增長

<property name="proxyFilters">
   <list>
      <ref bean="testFilter" />
   </list>
</property>

以及對應的bean

<bean id="testFilter" class="com.netease.urs.druid.filter.TestFilter"></bean>

運行程序,發現每次成功訪問數據庫都會打印"haha",看來這樣作的是對的。這樣,咱們就能夠按照本身的需求自定義一些操做啦(個人目的其實就是數據庫同步,把增、刪、改的sql語句加入隊列中)

原理解析

簡單分析下Druid的Filter吧。其實和通常的Filter同樣的(Spring Security,Servlet),功能就是在執行真正的業務代碼以前和以後增長一些自定義的功能。

順着剛纔的思路往上找,剛纔分析到了

@Override
public boolean preparedStatement_execute(FilterChain chain, PreparedStatementProxy statement) throws SQLException {
    try {
        //抽象方法
        statementExecuteBefore(statement, statement.getSql());
      
        //重點是這裏!!!!
        boolean firstResult = chain.preparedStatement_execute(statement);

        //抽象方法,後面咱們會重寫這個方法
        this.statementExecuteAfter(statement, statement.getSql(), firstResult);

        return firstResult;

    } catch (SQLException error) {
        //抽象方法
        statement_executeErrorAfter(statement, statement.getSql(), error);
        throw error;
    } catch (RuntimeException error) {
        //抽象方法
        statement_executeErrorAfter(statement, statement.getSql(), error);
        throw error;
    } catch (Error error) {
        //抽象方法
        statement_executeErrorAfter(statement, statement.getSql(), error);
        throw error;
    }

}

先來看一下FilterChain ,他只有一個實現類FilterChainImpl,有3個成員變量,終於看到了DataSourceProxy(看名字也知道其實就是對DruidDataSource的代理類)

protected int                 pos = 0;
private final DataSourceProxy dataSource;
private final int             filterSize;

preparedStatement_executeQuery的方法以下。這樣在調用前會把全部的Filter都執行一次其中的preparedStatement_execute。

@Override
public boolean preparedStatement_execute(PreparedStatementProxy statement) throws SQLException {
    if (this.pos < filterSize) {
        return nextFilter().preparedStatement_execute(this, statement);
    }
    return statement.getRawObject().execute();
}

preparedStatement_execute在哪裏被執行的呢?PreparedStatementProxyImpl#execute()

PreparedStatementProxyImpl#execute()在哪裏被執行的呢? DruidPooledPreparedStatement#execute()

(好吧,我認可我是debug看調用棧的。。DruidPooledPreparedStatement#execute()就是iBatis中的調用的了!)

調用的時序大概以下面

DruidPooledPreparedStatement除了execute(),還有executeUpdate(),executeQuery()。可是若是是基於ibatis的sql語句都是使用execute()。

相關文章
相關標籤/搜索