靜態代理是指由程式設計師創建或工具生成代理類的源碼,再編譯為 .class 檔案。代理類與目標類在編譯期就已確定關係,執行前代理類已存在。屬於結構型設計模式。
public interface UserDao {
void save(User user);
User findById(Long id);
List findAll();
}
public class UserDaoImpl implements UserDao {
private final DataSource dataSource;
public UserDaoImpl(DataSource dataSource) {
this.dataSource = dataSource;
}
@Override
public void save(User user) {
try (Connection conn = dataSource.getConnection();
PreparedStatement ps = conn.prepareStatement(
"INSERT INTO users(name, email) VALUES(?, ?)",
Statement.RETURN_GENERATED_KEYS)) {
ps.setString(1, user.getName());
ps.setString(2, user.getEmail());
ps.executeUpdate();
try (ResultSet rs = ps.getGeneratedKeys()) {
if (rs.next()) user.setId(rs.getLong(1));
}
} catch (SQLException e) {
throw new RuntimeException("保存用戶失敗", e);
}
}
@Override
public User findById(Long id) { /* 查詢實現 */ return null; }
@Override
public List findAll() { /* 查詢實現 */ return Collections.emptyList(); }
}
public class UserDaoProxy implements UserDao {
private final UserDao target; // 被代理對象
public UserDaoProxy(UserDao target) {
this.target = target;
}
@Override
public void save(User user) {
// 前置增強:日誌記錄
long start = System.currentTimeMillis();
log.info("開始保存用戶: {}", user.getName());
// 事務管理(簡化示意)
Connection conn = null;
try {
conn = dataSource.getConnection();
conn.setAutoCommit(false); // 開啟事務
// 委派真實對象執行核心邏輯
target.save(user);
conn.commit(); // 提交事務
log.info("用戶保存成功,耗時: {}ms", System.currentTimeMillis() - start);
} catch (Exception e) {
if (conn != null) try { conn.rollback(); } catch (SQLException ignored) {}
log.error("保存用戶失敗,事務回滾", e);
throw new RuntimeException(e);
} finally {
if (conn != null) try { conn.close(); } catch (SQLException ignored) {}
}
}
@Override
public User findById(Long id) {
log.debug("查詢用戶 ID: {}", id);
return target.findById(id);
}
@Override
public List findAll() {
log.debug("查詢所有用戶");
return target.findAll();
}
}
public class Client {
public static void main(String[] args) {
DataSource ds = new HikariDataSource(); // 連接池
UserDao realDao = new UserDaoImpl(ds);
UserDao proxyDao = new UserDaoProxy(realDao); // 包裝代理
User user = new User("張三", "zhangsan@example.com");
proxyDao.save(user); // 實際執行代理邏輯
// 客戶端無感知真實實現,面向介面編程
}
}
| 維度 | 靜態代理 | 動態代理 (JDK/CGLIB) |
|---|---|---|
| 代理類生成時間 | 編譯期 | 執行期 |
| 代碼形式 | 手寫/工具生成 .java | 字節碼動態生成 |
| 靈活性 | 低(每目標類需一代理類) | 高(統一攔截器處理多類) |
| 效能 | 最高(直接調用) | 略低(反射/MethodHandle 開銷) |
| 適用場景 | 固定、少量、效能敏感切面 | 通用、大量、框架級 AOP |
| 調試難度 | 易(可直接看代碼) | 難(動態字節碼) |
// 多層代理組合
UserDao dao = new UserDaoImpl(ds);
dao = new LoggingProxy(dao); // 第1層:日誌
dao = new TransactionProxy(dao); // 第2層:事務
dao = new CacheProxy(dao); // 第3層:緩存
dao = new RetryProxy(dao); // 第4層:重試
// 每層代理只關注單一職責,符合開閉原則
雖然 Spring AOP 預設使用動態代理,但以下場景仍用到靜態代理思想:
TransactionProxyFactoryBean (舊版) — XML 配置生成代理 Bean@Transactional 在非 public 方法/內部調用失效時,手寫代理類繞過限制RmiProxyFactoryBean、HttpInvokerProxyFactoryBean — 遠程服務靜態代理靜態代理雖不如動態代理靈活,但在效能敏感、調試友善、編譯期安全、輕量級切面場景不可替代。理解其原理有助於深入掌握 AOP 本質,也是動態代理、字節碼增強技術的基石。實際開發中,可結合使用:核心高頻路徑手寫靜態代理,通用橫切關注點交由 Spring AOP 動態代理。