1) 可以监控数据库访问性能,Druid内置提供了一个功能强大的StatFilter插件,能够详细统计SQL的执行性能,这对于线上分析数据库访问性能有帮助。
2) 替换DBCP和C3P0。Druid提供了一个高效、功能强大、可扩展性好的数据库连接池。
3) 数据库密码加密。直接把数据库密码写在配置文件中,这是不好的行为,容易导致安全问题。DruidDruiver和DruidDataSource都支持PasswordCallback。
4) SQL执行日志,Druid提供了不同的LogFilter,能够支持Common-Logging、Log4j和JdkLog,你可以按需要选择相应的LogFilter,监控你应用的数据库访问情况。
扩展JDBC,如果你要对JDBC层有编程的需求,可以通过Druid提供的Filter-Chain机制,很方便编写JDBC层的扩展插件。
-------------------------------------分割线--------------------------------------------
这才是本篇文章重点要描述的。
https://github.com/alibaba/druid/wiki/%E9%A6%96%E9%A1%B5 druid官方里面好像没找到。
不过用过druid的知道里面内置了一些filter
例如在配置
附内置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 filters = new CopyOnWriteArrayList();
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 filters) {
if (filters != null) {
this.filters.addAll(filters);
}
}
因此,我们在datasource配置中增加
以及对应的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();
}