數據庫連接池是 Java EE 應用性能的關鍵基礎設施。本文從零實現一個符合 javax.sql.DataSource 標準的連接池,並演示如何利用動態代理無侵入式增強 Connection.close() 行為(歸還而非真正關閉),同時對比繼承、裝飾器模式的優劣,幫你建立「池化思想」與「代理增強」的雙重認知。
public class MyPool implements DataSource {
// 連接池容器:LinkedList 支援高效首尾操作
public static List pool = new LinkedList();
// 靜態初始化:預創建 5 條連接
static {
try {
Class.forName("com.mysql.jdbc.Driver");
for (int i = 0; i < 5; i++) {
Connection conn = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/java", "root", "root");
pool.add(conn);
}
} catch (Exception e) { e.printStackTrace(); }
}
// 核心:獲取連接(池空時自動擴容)
@Override
public Connection getConnection() throws SQLException {
if (pool.size() == 0) { // 池耗盡自動補充
for (int i = 0; i < 5; i++) {
Connection conn = DriverManager.getConnection(...);
pool.add(conn);
}
}
Connection conn = pool.remove(0); // 取出首部(FIFO)
System.out.println("獲取連接,剩余:" + pool.size());
return conn;
}
// 關鍵:歸還連接(而非關閉)
public void resetConn(Connection conn) {
try {
if (conn != null && !conn.isClosed()) {
pool.add(conn); // 歸還池尾
System.out.println("歸還連接,剩余:" + pool.size());
}
} catch (Exception e) { e.printStackTrace(); }
}
// ... 其他 DataSource 方法拋出 UnsupportedOperationException 或返回 null
}
業務代碼習慣調用 conn.close(),但連接池需要 resetConn(conn)。動態代理完美解決:
// 測試類
public class JDBCTest {
public static void main(String[] args) throws Exception {
Class.forName("com.mysql.jdbc.Driver");
MyPool pool = new MyPool();
// 1. 獲取原始連接
Connection rawConn = pool.getConnection();
// 2. 創建代理:攔截 close() 轉調 resetConn()
Connection proxyConn = (Connection) Proxy.newProxyInstance(
Connection.class.getClassLoader(),
new Class[]{Connection.class},
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("close".equals(method.getName())) {
pool.resetConn(rawConn); // 歸還池
return null;
}
return method.invoke(rawConn, args); // 其他方法透傳
}
}
);
// 3. 業務代碼照常使用 proxyConn
Statement stmt = proxyConn.createStatement();
ResultSet rs = stmt.executeQuery("select * from user");
while (rs.next()) System.out.println(rs.getString("username"));
// 4. 調用 close() 實際歸還池
proxyConn.close(); // 不拋異常,不真正關閉底層連接
rs.close(); stmt.close();
}
}
| 模式 | 核心代碼 | 優點 | 缺點 | 適用場景 |
|---|---|---|---|---|
| 繼承 | class MyConn extends Connection | 簡單直觀 | 無法代理已有對象;最終類不可繼承 | 設計階段預知類結構 |
| 裝飾器 | class DecoratedConn implements Connection { Connection target; } | 編譯期類型安全;可組合多層裝飾 | 需手動實現所有接口方法(代碼量大) | 需靜態類型檢查、組合增強 |
| 動態代理 | Proxy.newProxyInstance(...) | 運行時生成;僅攔截關心方法;零侵入 | 僅支援接口;性能略低於直接調用 | 框架級增強(事務、日志、連接池) |
interface Animal { void eat(); void sound(); }
class Dog implements Animal {
public void eat() { System.out.println("吃"); }
public void sound() { System.out.println("汪"); }
}
class BigDog implements Animal { // 裝飾器
private Dog dog;
public BigDog(Dog dog) { this.dog = dog; }
public void eat() { dog.eat(); } // 透傳
public void sound() { System.out.println("大叫"); } // 增強
}
InvocationHandler.invoke() 中的「分派邏輯」DataSource/Connection 接口編程,才能無縫切換實現與注入代理動手跑通本文代碼,你將同時掌握「連接池內核機制」與「JDK 動態代理實戰」,為後續學習 Spring 事務管理、MyBatis 執行器、RPC 框架打下堅實基礎。