1) 能夠監控數據庫訪問性能,Druid內置提供了一個功能強大的StatFilter插件,可以詳細統計SQL的執行性能,這對於線上分析數據庫訪問性能有幫助。 node
2) 替換DBCP和C3P0。Druid提供了一個高效、功能強大、可擴展性好的數據庫鏈接池。 git
3) 數據庫密碼加密。直接把數據庫密碼寫在配置文件中,這是很差的行爲,容易致使安全問題。DruidDruiver和DruidDataSource都支持PasswordCallback。 github
4) SQL執行日誌,Druid提供了不一樣的LogFilter,可以支持Common-Logging、Log4j和JdkLog,你能夠按須要選擇相應的LogFilter,監控你應用的數據庫訪問狀況。 sql
擴展JDBC,若是你要對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()。