part4-druid-filter-chain
约 2975 字大约 10 分钟
2026-05-24
系列文章:《深入Spring事务与数据源:从注解到数据库连接的全链路解析》
系列二第4篇:Druid Filter链机制源码深度解析
基于版本:Druid 1.2.x、Spring Framework 5.3.x
前置阅读:系列二第3篇:连接获取与回收的完整链路
前言
前面三篇文章咱们把Druid连接池的核心功能都搞清楚了:架构设计、连接获取、连接归还。
但是,Druid还有个特别强大的功能咱们还没聊——Filter链机制。
你可能不知道,Druid之所以能做统计监控、SQL防火墙、日志记录,全靠这个Filter链!
而且我敢说,这个Filter链的设计,堪称教科书级别的责任链模式应用。搞懂了这个设计,你再去看其他框架的拦截器、过滤器,那简直就是秒杀!
今天,咱们就来深入Filter链的源码,看看它是怎么工作的。
一、回顾一下:Spring Boot是怎么配置Filter的?
还是老规矩,先看看怎么配置Filter。
核心入口:通过配置启用Filter
spring:
datasource:
druid:
# 启用StatFilter(统计监控)
filters: stat,wall,log4j2
# StatFilter配置
stat:
slow-sql-millis: 2000 # 慢SQL阈值
log-slow-sql: true # 记录慢SQL
# WallFilter配置
wall:
enabled: true
config:
delete-allow: false # 禁止DELETE
drop-table-allow: false # 禁止DROP TABLE看到没?通过filters属性配置要启用的Filter。
扩展场景1:如何通过代码配置Filter?
@Configuration
public class DruidDataSourceConfig {
@Bean
public DataSource dataSource() {
DruidDataSource dataSource = new DruidDataSource();
// 创建Filter列表
List<Filter> filters = new ArrayList<>();
// 添加StatFilter
StatFilter statFilter = new StatFilter();
statFilter.setSlowSqlMillis(2000);
statFilter.setLogSlowSql(true);
filters.add(statFilter);
// 添加WallFilter
WallFilter wallFilter = new WallFilter();
wallFilter.setEnabled(true);
filters.add(wallFilter);
// 设置Filter列表
dataSource.setProxyFilters(filters);
return dataSource;
}
}好了,配置搞清楚了,现在来看看Filter是怎么工作的!
二、Filter接口设计
先来看看Filter接口是怎么定义的:
核心接口1:Filter
文件位置:druid/src/main/java/com/alibaba/druid/filter/Filter.java
package com.alibaba.druid.filter;
import java.sql.*;
import java.util.List;
import java.util.Properties;
/**
* Filter接口
* 这是Druid Filter链的核心接口
* 所有Filter都必须实现这个接口
*/
public interface Filter {
/**
* 初始化Filter
*/
void init(FilterConfig config) throws SQLException;
/**
* 销毁Filter
*/
void destroy();
// ========== Connection相关方法 ==========
/**
* 连接创建后调用
*/
void connection_connect(FilterChain chain, Properties info) throws SQLException;
/**
* 连接关闭前调用
*/
void connection_close(FilterChain chain) throws SQLException;
// ========== Statement相关方法 ==========
/**
* Statement创建后调用
*/
void statement_create(FilterChain chain, int resultSetType,
int resultSetConcurrency, int resultSetHoldability) throws SQLException;
/**
* Statement执行SQL前调用
*/
void statement_execute(FilterChain chain, String sql) throws SQLException;
/**
* Statement执行SQL后调用
*/
void statement_executeAfter(FilterChain chain, String sql, boolean firstResult) throws SQLException;
// ========== ResultSet相关方法 ==========
/**
* ResultSet创建后调用
*/
void resultSet_open(FilterChain chain) throws SQLException;
/**
* ResultSet关闭前调用
*/
void resultSet_close(FilterChain chain) throws SQLException;
}看到这个接口,你可能会问:"这么多方法,每个Filter都要实现?太麻烦了吧!"
没错,所以Druid提供了一个适配器类——FilterAdapter:
核心类1:FilterAdapter
文件位置:druid/src/main/java/com/alibaba/druid/filter/FilterAdapter.java
package com.alibaba.druid.filter;
import java.sql.*;
import java.util.Properties;
/**
* Filter适配器
* 提供所有方法的空实现
* 子类只需要覆盖需要的方法
*/
public class FilterAdapter implements Filter {
@Override
public void init(FilterConfig config) throws SQLException {
// 空实现
}
@Override
public void destroy() {
// 空实现
}
@Override
public void connection_connect(FilterChain chain, Properties info) throws SQLException {
// 默认实现:直接调用下一个Filter
chain.connection_connect(info);
}
@Override
public void connection_close(FilterChain chain) throws SQLException {
// 默认实现:直接调用下一个Filter
chain.connection_close();
}
@Override
public void statement_execute(FilterChain chain, String sql) throws SQLException {
// 默认实现:直接调用下一个Filter
chain.statement_execute(sql);
}
// ... 其他方法都是类似的模式
}看到了吧?FilterAdapter提供了所有方法的默认实现,就是简单地调用下一个Filter。
这样,具体的Filter只需要继承FilterAdapter,覆盖需要的方法就行了。
扩展场景2:自定义Filter
public class MyFilter extends FilterAdapter {
@Override
public void statement_execute(FilterChain chain, String sql) throws SQLException {
// 执行前
System.out.println("执行SQL: " + sql);
long startTime = System.currentTimeMillis();
// 调用下一个Filter
chain.statement_execute(sql);
// 执行后
long endTime = System.currentTimeMillis();
System.out.println("执行耗时: " + (endTime - startTime) + "ms");
}
}三、FilterChain:Filter链的核心
好了,Filter接口搞清楚了,现在来看Filter链是怎么工作的。
核心类2:FilterChainImpl
文件位置:druid/src/main/java/com/alibaba/druid/filter/FilterChainImpl.java
package com.alibaba.druid.filter;
import java.sql.*;
import java.util.List;
import java.util.Properties;
/**
* Filter链的实现
* 负责串联执行所有Filter
*/
public class FilterChainImpl implements FilterChain {
// 数据源
private DruidDataSource dataSource;
// Filter列表
private List<Filter> filters;
// 当前执行到的Filter索引
private int pos;
public FilterChainImpl(DruidDataSource dataSource, List<Filter> filters) {
this.dataSource = dataSource;
this.filters = filters;
this.pos = 0;
}
/**
* 执行连接创建
*/
@Override
public void connection_connect(Properties info) throws SQLException {
// ★★★★★ 核心:如果还有Filter,执行当前Filter ★★★★★
if (this.pos < filters.size()) {
// 获取当前Filter
Filter filter = filters.get(pos++);
// ★★★★★ 执行Filter,传入chain(自己)★★★★★
// 这样Filter内部可以调用chain.connection_connect()继续执行
filter.connection_connect(this, info);
} else {
// 所有Filter执行完毕,执行实际的连接创建
pos = 0; // 重置索引
dataSource.createPhysicalConnection(info);
}
}
/**
* 执行Statement执行SQL
*/
@Override
public void statement_execute(String sql) throws SQLException {
if (this.pos < filters.size()) {
Filter filter = filters.get(pos++);
// 执行Filter
filter.statement_execute(this, sql);
} else {
// 所有Filter执行完毕,执行实际的方法
pos = 0;
// 实际执行SQL
}
}
// ... 其他方法类似
}看到这个实现,你可能会觉得:"这有啥特别的?"
但是,请注意这个设计的精妙之处——递归调用!
关键点:Filter内部调用chain.xxx(),又会回到FilterChainImpl,然后执行下一个Filter。
这就是经典的责任链模式!
四、Filter链的执行流程
好,现在来看Filter链是怎么串联执行的。
假设配置了3个Filter:StatFilter、WallFilter、LogFilter。
执行流程:
调用:statement.execute(sql)
↓
┌──────────────────────────────────────┐
│ FilterChainImpl.statement_execute() │
│ │
│ pos = 0 │
│ filters.size() = 3 │
└─────────────┬────────────────────────┘
↓
┌──────────────────────────────────────┐
│ filter = filters.get(0) │
│ → StatFilter │
│ │
│ pos++ (变成1) │
│ filter.statement_execute(this, sql) │
└─────────────┬────────────────────────┘
↓
┌──────────────────────────────────────┐
│ StatFilter.statement_execute() │
│ │
│ 1. 记录开始时间 │
│ 2. chain.statement_execute(sql) │
│ → 回到FilterChainImpl │
└─────────────┬────────────────────────┘
↓
┌──────────────────────────────────────┐
│ FilterChainImpl.statement_execute() │
│ │
│ pos = 1 │
│ filter = filters.get(1) │
│ → WallFilter │
│ │
│ pos++ (变成2) │
│ filter.statement_execute(this, sql) │
└─────────────┬────────────────────────┘
↓
┌──────────────────────────────────────┐
│ WallFilter.statement_execute() │
│ │
│ 1. 检查SQL合法性 │
│ 2. chain.statement_execute(sql) │
│ → 回到FilterChainImpl │
└─────────────┬────────────────────────┘
↓
┌──────────────────────────────────────┐
│ FilterChainImpl.statement_execute() │
│ │
│ pos = 2 │
│ filter = filters.get(2) │
│ → LogFilter │
│ │
│ pos++ (变成3) │
│ filter.statement_execute(this, sql) │
└─────────────┬────────────────────────┘
↓
┌──────────────────────────────────────┐
│ LogFilter.statement_execute() │
│ │
│ 1. 记录SQL日志 │
│ 2. chain.statement_execute(sql) │
│ → 回到FilterChainImpl │
└─────────────┬────────────────────────┘
↓
┌──────────────────────────────────────┐
│ FilterChainImpl.statement_execute() │
│ │
│ pos = 3 │
│ filters.size() = 3 │
│ pos >= filters.size() │
│ │
│ → 执行实际方法 │
│ → 真正执行SQL │
└──────────────────────────────────────┘看到没?执行顺序是:
StatFilter.before → WallFilter.before → LogFilter.before
→ 实际执行
→ LogFilter.after → WallFilter.after → StatFilter.after这就是责任链模式的精髓——层层拦截,层层传递!
扩展场景3:如果某个Filter中断执行怎么办?
public class WallFilter extends FilterAdapter {
@Override
public void statement_execute(FilterChain chain, String sql) throws SQLException {
// 检查SQL合法性
if (!isValid(sql)) {
// 如果SQL不合法,直接抛异常,不调用chain.statement_execute()
throw new SQLException("SQL不合法: " + sql);
}
// SQL合法,继续执行
chain.statement_execute(sql);
}
}如果Filter内部不调用chain.xxx(),责任链就中断了!
五、StatFilter:统计监控的实现
好了,Filter链的工作原理搞清楚了,现在来看具体的Filter实现。
第一个要看的,就是最常用的StatFilter——统计监控。
核心类3:StatFilter
文件位置:druid/src/main/java/com/alibaba/druid/filter/stat/StatFilter.java
package com.alibaba.druid.filter.stat;
import com.alibaba.druid.filter.FilterAdapter;
import com.alibaba.druid.filter.FilterChain;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.stat.DruidDataSourceStatManager;
import java.sql.SQLException;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicLong;
/**
* 统计监控Filter
* 负责统计SQL执行情况、慢SQL等
*/
public class StatFilter extends FilterAdapter {
// 慢SQL阈值(毫秒)
private long slowSqlMillis = 3000;
// 是否记录慢SQL
private boolean logSlowSql = false;
@Override
public void init(FilterConfig config) throws SQLException {
// 初始化统计数据
DruidDataSourceStatManager.addDataSource(config.getDataSource());
}
@Override
public void statement_execute(FilterChain chain, String sql) throws SQLException {
// ========== 第一步:记录开始时间 ==========
long startNanos = System.nanoTime();
try {
// ========== 第二步:调用下一个Filter ==========
chain.statement_execute(sql);
} finally {
// ========== 第三步:计算执行耗时 ==========
long elapsedNanos = System.nanoTime() - startNanos;
long elapsedMillis = elapsedNanos / (1000 * 1000);
// ========== 第四步:统计数据 ==========
// 更新执行次数
getDataSourceStat().getExecuteCount().incrementAndGet();
// 更新总耗时
getDataSourceStat().getTotalTime().addAndGet(elapsedNanos);
// ========== 第五步:检查是否是慢SQL ==========
if (elapsedMillis >= slowSqlMillis) {
// 慢SQL
getDataSourceStat().getSlowSqlCount().incrementAndGet();
// 记录慢SQL日志
if (logSlowSql) {
LOG.error("slow sql: " + sql + " executed in " + elapsedMillis + "ms");
}
}
}
}
// ... 其他统计方法
}StatFilter的工作很简单:
- 记录开始时间
- 调用下一个Filter
- 计算执行耗时
- 更新统计数据
- 检查是否是慢SQL
扩展场景4:Druid的监控页面是怎么实现的?
// 统计数据保存在DruidDataSourceStatManager中
public class DruidDataSourceStatManager {
// 所有数据源的统计数据
private static Map<String, DruidDataSourceStatValue> dataSources =
new ConcurrentHashMap<>();
public static void addDataSource(DruidDataSource dataSource) {
dataSources.put(dataSource.getName(), dataSource.getStatValue());
}
public static Map<String, DruidDataSourceStatValue> getDataSources() {
return dataSources;
}
}
// 监控页面通过Servlet读取这些数据
@WebServlet(urlPatterns = "/druid/*")
public class StatViewServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
Map<String, DruidDataSourceStatValue> data =
DruidDataSourceStatManager.getDataSources();
// 渲染HTML页面
renderHtml(resp, data);
}
}所以,你在浏览器访问/druid/index.html看到的监控数据,就是从这里读取的!
六、WallFilter:SQL防火墙的实现
第二个重要的Filter是WallFilter——SQL防火墙。
核心类4:WallFilter
文件位置:druid/src/main/java/com/alibaba/druid/wall/WallFilter.java
package com.alibaba.druid.wall;
import com.alibaba.druid.filter.FilterAdapter;
import com.alibaba.druid.filter.FilterChain;
import java.sql.SQLException;
/**
* SQL防火墙Filter
* 负责检查SQL合法性,防止SQL注入等攻击
*/
public class WallFilter extends FilterAdapter {
// SQL检查器
private WallProvider provider;
// 是否启用
private boolean enabled = true;
@Override
public void init(FilterConfig config) throws SQLException {
// 初始化SQL检查器
provider = new WallProvider();
}
@Override
public void statement_execute(FilterChain chain, String sql) throws SQLException {
if (!enabled) {
// 如果未启用,直接执行
chain.statement_execute(sql);
return;
}
// ========== 第一步:检查SQL合法性 ==========
WallCheckResult result = provider.check(sql);
if (!result.isViolation()) {
// SQL合法,继续执行
chain.statement_execute(sql);
} else {
// SQL不合法,抛异常
throw new SQLException("SQL violation: " + result.getViolationMessage());
}
}
}WallFilter的核心是WallProvider.check(),它会解析SQL,检查是否有违规行为。
扩展场景5:WallFilter能检测哪些违规行为?
// 1. 禁止DELETE
config.setDeleteAllow(false);
// DELETE FROM user WHERE id = 1 → 抛异常
// 2. 禁止DROP TABLE
config.setDropTableAllow(false);
// DROP TABLE user → 抛异常
// 3. 禁止批量执行
config.setMultiStatementAllow(false);
// DELETE FROM user; DELETE FROM order; → 抛异常
// 4. 禁止注释
config.setCommentAllow(false);
// SELECT * FROM user WHERE id = 1 /* 注释 */ → 抛异常
// 5. 禁止危险函数
config.setDangerousFunctions(true);
// SELECT LOAD_FILE('/etc/passwd') → 抛异常七、Filter的加载与初始化
好了,具体的Filter实现搞清楚了,现在来看Filter是怎么被加载和初始化的。
核心方法:initFromSPIServiceLoader
文件位置:druid/src/main/java/com/alibaba/druid/pool/DruidDataSource.java
private void initFromSPIServiceLoader() {
// 通过SPI加载Filter
ServiceLoader<Filter> serviceLoader = ServiceLoader.load(Filter.class);
for (Filter filter : serviceLoader) {
// 添加到Filter列表
filters.add(filter);
}
}扩展场景6:如何通过SPI注册自定义Filter?
- 创建Filter实现类:
public class MyFilter extends FilterAdapter {
// 实现
}- 在
META-INF/services/com.alibaba.druid.filter.Filter文件中写入:
com.example.MyFilter- ServiceLoader会自动加载这个Filter。
八、完整的Filter链架构图
┌──────────────────────────────────────────────┐
│ DruidDataSource │
│ │
│ filters列表: │
│ [0] → StatFilter │
│ [1] → WallFilter │
│ [2] → LogFilter │
│ │
│ FilterChainImpl │
│ ┌─────────────────────────────────┐ │
│ │ pos = 0 │ │
│ │ │ │
│ │ statement_execute(sql) │ │
│ │ ↓ │ │
│ │ filters[0].statement_execute() │ │
│ │ → StatFilter │ │
│ │ → chain.statement_execute() │ │
│ │ ↓ │ │
│ │ filters[1].statement_execute()│ │
│ │ → WallFilter │ │
│ │ → chain.statement_execute() │ │
│ │ ↓ │ │
│ │ filters[2].statement_execute() │
│ │ → LogFilter │ │
│ │ → chain.statement_execute() │
│ │ ↓ │ │
│ │ 实际执行SQL │ │
│ └─────────────────────────────────┘ │
└──────────────────────────────────────────────┘九、关键问题解答
Q1:Filter的执行顺序是怎么确定的?
答案:按照filters列表的顺序:
List<Filter> filters = new ArrayList<>();
filters.add(new StatFilter()); // 第一个执行
filters.add(new WallFilter()); // 第二个执行
filters.add(new LogFilter()); // 第三个执行Q2:如果Filter抛异常怎么办?
答案:异常会向上传播,中断执行:
try {
chain.statement_execute(sql);
} catch (SQLException e) {
// Filter抛的异常,会被调用方捕获
throw e;
}Q3:Filter链的性能开销大吗?
答案:很小,因为只是方法调用,没有额外的开销。
一般来说,10个Filter的开销不到1毫秒。
Q4:可以动态添加/移除Filter吗?
答案:可以,但不推荐。一般在初始化时配置好。
十、总结与伏笔
通过这篇文章的分析,咱们搞清楚了Druid的Filter链机制:
- Filter接口设计:定义了所有拦截点
- FilterAdapter适配器:提供空实现,简化开发
- FilterChain责任链:串联执行所有Filter
- StatFilter统计监控:记录SQL执行情况、慢SQL
- WallFilter SQL防火墙:检查SQL合法性
- SPI加载机制:自动发现和加载Filter
但是,咱们还有个关键问题没深入:
连接的有效性是怎么检测的?
- 心跳检测是怎么实现的?
- 连接保活机制是怎么工作的?
- Druid的监控页面是怎么渲染的?
- 性能调优有哪些最佳实践?
这些问题,将在下一篇文章《连接有效性检测与性能调优》中为你揭晓。这也是系列二的收官之作!
敬请期待!
参考资料
- Druid 1.2.x 源码
src/main/java/com/alibaba/druid/filter/Filter.javasrc/main/java/com/alibaba/druid/filter/FilterChainImpl.javasrc/main/java/com/alibaba/druid/filter/stat/StatFilter.javasrc/main/java/com/alibaba/druid/wall/WallFilter.java
- 《阿里巴巴Druid官方文档》
- 《设计模式》- 责任链模式