核心思路是在代理对象创建时捕获并保存当前线程堆栈快照,通过tracingconnectionproxy封装真实connection,在构造时调用thread.currentthread().getstacktrace()记录调用点,供泄漏排查与定位使用。

在 Java 中,若想通过自定义 ConnectionProxy 记录连接获取时的调用堆栈(例如排查连接泄漏、定位哪段代码打开了未关闭的连接),核心思路是:**在代理对象创建时捕获并保存当前线程的堆栈快照**,后续可通过日志、监控或调试接口暴露该信息。
1. 为什么需要记录堆栈
连接池(如 HikariCP、Druid、Apache DBCP)中的连接被复用,真实 Connection 对象会被多次包装。仅靠 toString() 或简单标识无法追溯“谁在什么时候申请了这个连接”。记录堆栈可精准定位到业务代码中 dataSource.getConnection() 的调用位置,尤其在连接长期未关闭导致泄漏时非常关键。
2. 自定义 ConnectionProxy 的基本结构
以 JDK 动态代理为例,代理类需实现 Connection 接口,并在构造时捕获堆栈:
public class TracingConnectionProxy implements Connection {
private final Connection target;
private final StackTraceElement[] creationStackTrace;
public TracingConnectionProxy(Connection target) {
this.target = target;
// 捕获调用 new TracingConnectionProxy(...) 时的堆栈
this.creationStackTrace = Thread.currentThread().getStackTrace();
}
// 重写 close():可选择记录归还堆栈,或仅标记为已关闭
@Override
public void close() throws SQLException {
// 可选:记录 close 调用点,用于比对是否匹配
if (target != null && !target.isClosed()) {
target.close();
}
}
// 其他方法委托给 target,例如:
@Override
public Statement createStatement() throws SQLException {
return target.createStatement();
}
// 提供访问堆栈的方法(供日志/健康检查使用)
public StackTraceElement[] getCreationStackTrace() {
return creationStackTrace.clone();
}
}
3. 在连接池中注入代理逻辑
不同连接池注入方式不同,常见做法:
-
HikariCP:不直接支持自定义 Proxy,但可通过
DataSource包装实现。例如实现一个TracingHikariDataSource,重写getConnection(),返回包装后的TracingConnectionProxy。 -
Druid:提供
Filter扩展机制。继承FilterEventAdapter,重写connection_connect方法,在连接创建后包装成自定义代理,并存入ConnectionHolder的属性中。 -
自研/轻量池:在
poll()或createConnection()返回前,显式 wrap 成TracingConnectionProxy即可。
4. 输出与诊断建议
堆栈本身不自动打印,需主动触发。推荐以下实践:
- 在连接 close 时检查是否已泄露(如超过阈值未 close),然后打印
getCreationStackTrace(); - 提供 JMX 或 HTTP 端点(如
/actuator/leaked-connections),列出所有活跃代理及其创建堆栈; - 结合 SLF4J,在连接创建时按 INFO 级别记录(建议加开关控制,避免性能影响):
log.info("New traced connection created by: {}", Arrays.toString(stack[3]));(跳过代理和池内部帧); - 过滤掉 JDBC 驱动、连接池、代理自身的堆栈帧,只保留业务包路径(如
com.yourcompany.service.*),提升可读性。
不复杂但容易忽略的是:堆栈捕获要放在代理实例化的一刻,而不是 getConnection() 方法入口——否则可能被池内复用逻辑干扰;同时注意避免在高并发下频繁调用 getStackTrace() 带来开销,可考虑采样或仅在 debug 模式启用。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











