Filter几乎已经是中间件应用扩展功能的必备项了。
无论是Dubbo、Spring、Tomcat等等优秀框架,几乎都有对应的Filter设计。Filter通常是FilterChain的形式串在一起,可以几乎无侵入的实现框架的扩展。
既然Druid也宣称功能强大易扩展,那不可避免的也会使用到Filter的概念。实际在之前的博客中,笔者刻意没有贴出Filter相关代码。
在本文中我们来从源码角度了解下Filter的使用。
1.代码准备DruidDataSource dataSource = new DruidDataSource();
// 以下四个参数为必输项
dataSource.setUrl("jdbc:mysql://localhost:3306/db1");
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUsername("root");
dataSource.setPassword("root");
// 1.特意添加Filter
dataSource.setFilters("stat,trace,log4j,encoding");
// 2.也可以不特意添加Filter,datasource在init时会扫描对应的Filter实现
Connection connection = dataSource.getConnection();
Statement stmt = connection.createStatement();
stmt.execute("select 1;");
ResultSet rs = stmt.getResultSet();
Assert.assertFalse(rs.isClosed());
1.1 dataSource.setFilters加载方式
// DruidDataSource.setFilters
public void setFilters(String filters) throws SQLException {
if (filters != null && filters.startsWith("!")) {
filters = filters.substring(1);
this.clearFilters();
}
this.addFilters(filters);
}
// DruidDataSource.addFilters
public void addFilters(String filters) throws SQLException {
String[] filterArray = filters.split("\\,");
for (String item : filterArray) {
// 分割后加载
FilterManager.loadFilter(this.filters, item.trim());
}
}
// FilterManager.loadFilter
public static void loadFilter(List filters, String filterName) throws SQLException {
if (filterName.length() == 0) {
return;
}
// 从alias中获取,alias中的Filter是在static方法中加载的,
// 具体见FilterManager.loadFilterConfig()方法
String filterClassNames = getFilter(filterName);
if (filterClassNames != null) {
for (String filterClassName : filterClassNames.split(",")) {
...
Class filterClass = Utils.loadClass(filterClassName);
Filter filter;
try {
// 通过反射来获取对应的Filter类
filter = (Filter) filterClass.newInstance();
}
...
filters.add(filter);
}
return;
}
...
}
注意:FilterManager.java static方法默认加载的Filter列表如下,默认从
META-INF/druid-filter.properties文件中加载,文件内容如下:
druid.filters.default=com.alibaba.druid.filter.stat.StatFilter
druid.filters.stat=com.alibaba.druid.filter.stat.StatFilter
druid.filters.mergeStat=com.alibaba.druid.filter.stat.MergeStatFilter
druid.filters.counter=com.alibaba.druid.filter.stat.StatFilter
druid.filters.encoding=com.alibaba.druid.filter.encoding.EncodingConvertFilter
druid.filters.log4j=com.alibaba.druid.filter.logging.Log4jFilter
druid.filters.log4j2=com.alibaba.druid.filter.logging.Log4j2Filter
druid.filters.slf4j=com.alibaba.druid.filter.logging.Slf4jLogFilter
druid.filters.commonlogging=com.alibaba.druid.filter.logging.CommonsLogFilter
druid.filters.commonLogging=com.alibaba.druid.filter.logging.CommonsLogFilter
druid.filters.wall=com.alibaba.druid.wall.WallFilter
druid.filters.config=com.alibaba.druid.filter.config.ConfigFilter
druid.filters.haRandomValidator=com.alibaba.druid.pool.ha.selector.RandomDataSourceValidateFilter
总结:可以看到,通过setFilters的方式,直接从FilterManager中已经加载好的Filter中来获取。
1.2 DruidDataSource.init方法主动加载可用Filter// DruidDataSource.init
public void init() throws SQLException {
if (inited) {
return;
}
...
// DruidDataSource.setProxyFilters()方法对filters进行赋值
for (Filter filter : filters) {
filter.init(this);
}
...
// 在这里通过SPI的方式来扫描
initFromSPIServiceLoader();
...
}
// DruidDataSource.initFromSPIServiceLoader
private void initFromSPIServiceLoader() {
if (loadSpifilterSkip) {
return;
}
if (autoFilters == null) {
List filters = new ArrayList();
// 通过ServiceLoader的方式来加载Filter
ServiceLoader autoFilterLoader = ServiceLoader.load(Filter.class);
// 解析Filter,并添加到filters中
for (Filter filter : autoFilterLoader) {
AutoLoad autoLoad = filter.getClass().getAnnotation(AutoLoad.class);
if (autoLoad != null && autoLoad.value()) {
filters.add(filter);
}
}
autoFilters = filters;
}
...
}
总结:ServiceLoader有点类似于ClassLoader,只不过ServiceLoader只加载特定的类。这种低侵入加载类的方式被广泛应用在Dubbo和Druid中。
我们可以自定义一个Filter的实现,然后将其类全限定名放入META-INF/services目录下的com.alibaba.druid.filter.Filter文件中即可,ServiceLoader即会主动加载该实现。
总结:通过上述两种方式,我们可以实现Filter的加载,以实现Druid功能的扩展。在我们项目中通常是通过第二种方式来主动无侵入的加载。
2.Filter在创建连接时的使用Filter在加载之后,什么时候会被使用到呢?我们来看下代码(采用上述1.1中的示例)
// DruidDataSource.getConnection
public DruidPooledConnection getConnection(long maxWaitMillis) throws SQLException {
init();
// 若当前项目有Filter被加载到,则不再使用直接获取连接的方式
if (filters.size() > 0) {
FilterChainImpl filterChain = new FilterChainImpl(this);
return filterChain.dataSource_connect(this, maxWaitMillis);
} else {
return getConnectionDirect(maxWaitMillis);
}
}
// FilterChainImpl.dataSource_connect
public DruidPooledConnection dataSource_connect(DruidDataSource dataSource, long maxWaitMillis) throws SQLException {
// 每次获取连接时FilterChainImpl都是新new出来的,则pos默认都是从0开始的
if (this.pos < filterSize) {
DruidPooledConnection conn = nextFilter().dataSource_getConnection(this, dataSource, maxWaitMillis);
return conn;
}
return dataSource.getConnectionDirect(maxWaitMillis);
}
FilterChainImpl是责任链模式,我们来具体看下
// 1.LogFilter.dataSource_getConnection
public DruidPooledConnection dataSource_getConnection(FilterChain chain, DruidDataSource dataSource,
long maxWaitMillis) throws SQLException {
// 调用chain.dataSource_connect方法,本质上执行下一个Filter.dataSource_getConnection方法
DruidPooledConnection conn = chain.dataSource_connect(dataSource, maxWaitMillis);
ConnectionProxy connection = (ConnectionProxy) conn.getConnectionHolder().getConnection();
if (connectionConnectAfterLogEnable && isConnectionLogEnabled()) {
connectionLog("{conn-" + connection.getId() + "} pool-connect");
}
return conn;
}
// 2.StatFilter.dataSource_getConnection
public DruidPooledConnection dataSource_getConnection(FilterChain chain, DruidDataSource dataSource,
long maxWaitMillis) throws SQLException {
// 也是回调到chain来执行
DruidPooledConnection conn = chain.dataSource_connect(dataSource, maxWaitMillis);
if (conn != null) {
conn.setConnectedTimeNano();
StatFilterContext.getInstance().pool_connection_open();
}
return conn;
}
...
dataSource.getConnectionDirect(maxWaitMillis)方法,直接获取到连接后,
然后再执行对应Filter的后续方法(比如LogFilter会打印log,
StateFilter会创建连接的ConnectedTimeNano参数)
// 执行代码如下
Statement stmt = connection.createStatement();
stmt.execute("select 1;");
该Statement的实现类为StatementProxyImpl,我们来看下其execute方法
// StatementProxyImpl.execute
public boolean execute(String sql) throws SQLException {
...
// 同获取Connection一样,执行SQL前也是先获取一个FilterChain
FilterChainImpl chain = createChain();
firstResultSet = chain.statement_execute(this, sql);
recycleFilterChain(chain);
return firstResultSet;
}
// FilterChainImpl.statement_execute
public boolean statement_execute(StatementProxy statement, String sql) throws SQLException {
// 这里是不是很眼熟,默认也会执行Filter.statement_execute方法
if (this.pos < filterSize) {
return nextFilter().statement_execute(this, statement, sql);
}
return statement.getRawObject().execute(sql);
}
继续责任链模式的使用
// 1.LogFilter.statement_execute
public boolean statement_execute(FilterChain chain, StatementProxy statement, String sql) throws SQLException {
statementExecuteBefore(statement, sql);
try {
boolean firstResult = super.statement_execute(chain, statement, sql);
statementExecuteAfter(statement, sql, firstResult);
return firstResult;
}
...
}
// LogFilter.statementExecuteBefore
protected void statementExecuteBefore(StatementProxy statement, String sql) {
// 写入Statement.lastExecuteStartNano参数
statement.setLastExecuteStartNano();
if (statement instanceof PreparedStatementProxy) {
logParameter((PreparedStatementProxy) statement);
}
}
// LogFilter.statementExecuteAfter
protected void statementExecuteAfter(StatementProxy statement, String sql, boolean firstResult) {
logExecutableSql(statement, sql);
if (statementExecuteAfterLogEnable && isStatementLogEnabled()) {
statement.setLastExecuteTimeNano();
double nanos = statement.getLastExecuteTimeNano();
double millis = nanos / (1000 * 1000);
// SQL执行完毕后,写入log,记录SQL执行时间
statementLog("{conn-" + statement.getConnectionProxy().getId() + ", " + stmtId(statement) + "} executed. "
+ millis + " millis. " + sql);
}
}
可以看到,LogFilter即在执行SQL之前和之后记录当前时间,并将SQL的真正执行时间记录到日志中。
其他Filter也是类似的,笔者不再继续分析。
4.自定义Filter有时候我们需要对Druid功能进行扩展,最好的方式就是按照之前1.2的方式定义Filter,并将其放入META-INF/services目录下的com.alibaba.druid.filter.Filter文件中。
Filter的实现并不复杂,并且Druid作者提供了几种Filter的实现接口和抽象类。
1)Filter接口(我们需要实现所有的方法,比较麻烦,不建议直接用)
2)FilterAdapter抽象类(实现了大部分的方法,基本都是statement的相关实现)
3)FilterEventAdapter抽象类(更强大的实现,不仅实现了Filter接口的大部分方法,而且提供了更加规范的方式来完成对连接和statement的前置和后置处理),通过源码来感受下其强大
public abstract class FilterEventAdapter extends FilterAdapter {
public FilterEventAdapter(){
}
public ConnectionProxy connection_connect(FilterChain chain, Properties info) throws SQLException {
connection_connectBefore(chain, info);
ConnectionProxy connection = super.connection_connect(chain, info);
connection_connectAfter(connection);
return connection;
}
public void connection_connectBefore(FilterChain chain, Properties info) {
}
public void connection_connectAfter(ConnectionProxy connection) {
}
@Override
public StatementProxy connection_createStatement(FilterChain chain, ConnectionProxy connection) throws SQLException {
StatementProxy statement = super.connection_createStatement(chain, connection);
statementCreateAfter(statement);
return statement;
}
...
}
总结:建议大家在自定义Filter时,实现FilterEventAdapter抽象类,以更好的扩展Druid