老代碼裡常見 Class.forName("com.mysql.jdbc.Driver"),新版教程卻說「不需要了」。本文深入剖析反射加載的歷史使命、JDBC 4.0 SPI 機制、解耦設計哲學,以及驅動類靜態代碼塊的註冊邏輯。
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
// 驅動 JAR 不在 classpath,給出友好提示或降級
}
三大作用:
ClassNotFoundException,可捕獲處理(如提示用戶下載)<clinit> 靜態代碼塊,完成驅動註冊import com.mysql.jdbc.Driver,僅依賴 java.sql.* 接口package com.mysql.jdbc;
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
// 類加載時自動執行
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException e) {
throw new RuntimeException("Can't register driver!");
}
}
}
DriverManager.registerDriver(driver) 將實例存入 registeredDrivers(CopyOnWriteArrayList),後續 getConnection(url) 遍歷列表調用 driver.acceptsURL(url) 匹配。
| 維度 | import 硬依賴 | Class.forName 反射 |
|---|---|---|
| 編譯期 | 必須有驅動 JAR | 無需驅動 JAR |
| 運行期切換數據庫 | 需修改代碼重編譯 | 只需換配置文件類名 |
| 符合 OCP 原則 | ❌ 違反(依賴具體實現) | ✅ 面向接口編程 |
| 配置外部化 | 困難 | 天然支援(properties/yaml) |
最佳實踐:驅動類名寫在配置文件(jdbc.driver=com.mysql.cj.jdbc.Driver),啟動時讀取反射加載,實現「零代碼修改切換 MySQL/Oracle/PostgreSQL」。
META-INF/services/java.sql.Driver 文件列出實現類全限定名DriverManager 初始化時,通過 ServiceLoader.load(Driver.class) 自動發現並實例化# mysql-connector-java-8.0.33.jar!/META-INF/services/java.sql.Driver
com.mysql.cj.jdbc.Driver
| MySQL 版本 | 驅動類名 | Maven 坐標 | 是否需 Class.forName |
|---|---|---|---|
| 5.x | com.mysql.jdbc.Driver | mysql:mysql-connector-java:5.1.x | 建議保留(兼容舊環境) |
| 8.0+ | com.mysql.cj.jdbc.Driver | com.mysql:mysql-connector-j:8.x | ❌ 不需(SPI 自動發現) |
除驅動加載,反射是「ResultSet → Entity」自動映射的基石:
public T mapRow(ResultSet rs, Class clazz) throws Exception {
T obj = clazz.getDeclaredConstructor().newInstance();
ResultSetMetaData meta = rs.getMetaData();
for (int i = 1; i <= meta.getColumnCount(); i++) {
String col = meta.getColumnLabel(i);
Field field = clazz.getDeclaredField(col);
field.setAccessible(true);
field.set(obj, rs.getObject(i));
}
return obj;
}
這正是 MyBatis、Hibernate、JdbcTemplate 等框架的核心實現邏輯。
Class.forName 歷史使命:運行期檢查、觸發註冊、解耦編譯依賴