代理模式為其他物件提供代理以控制存取,是 Spring AOP 的核心基礎。本文完整實作三種代理方式,深度對比原理、優缺點與適用場景。
public interface IUserDao {
void save();
}
特點:代理類別與目標類別實作同一介面,編譯期確定關係。
// 目標物件
public class UserDao implements IUserDao {
public void save() {
System.out.println("-----已經保存數據!!!------");
}
}
// 代理物件
public class UserDaoProxy implements IUserDao {
private IUserDao target;
public UserDaoProxy(IUserDao target) { this.target = target; }
public void save() {
System.out.println("開始事務...");
target.save(); // 執行目標方法
System.out.println("提交事務...");
}
}
// 測試
public class App {
public static void main(String[] args) {
IUserDao target = new UserDao();
IUserDao proxy = new UserDaoProxy(target);
proxy.save();
}
}
缺點:每個目標類別需手寫代理類別,程式碼冗餘、維護成本高。
核心:java.lang.reflect.Proxy + InvocationHandler,執行期動態生成代理類別。
public class ProxyFactory {
private Object target;
public ProxyFactory(Object target) { this.target = target; }
public Object getProxyInstance() {
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("開啟事務");
Object returnValue = method.invoke(target, args); // 反射呼叫目標
System.out.println("提交事務");
return returnValue;
}
}
);
}
}
// 測試
public class App {
public static void main(String[] args) {
IUserDao target = new UserDao();
IUserDao proxy = (IUserDao) new ProxyFactory(target).getProxyInstance();
System.out.println(proxy.getClass()); // class com.sun.proxy.$Proxy0
proxy.save();
}
}
限制:目標類別必須實作介面,代理只能代理介面方法。
核心:繼承目標類別,覆寫非 final 方法,無需介面。需引入 cglib 或 spring-core 依賴。
public class ProxyFactory implements MethodInterceptor {
private Object target;
public ProxyFactory(Object target) { this.target = target; }
public Object getProxyInstance() {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.getClass()); // 設定父類別
enhancer.setCallback(this); // 設定攔截器
return enhancer.create(); // 建立子類別代理
}
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("開始事務.....");
Object returnValue = method.invoke(target, args);
System.out.println("提交事務.....");
return returnValue;
}
}
// 測試(目標類別無需實作介面)
public class App {
public static void main(String[] args) {
UserDao target = new UserDao(); // 普通類別
UserDao proxy = (UserDao) new ProxyFactory(target).getProxyInstance();
System.out.println(proxy.getClass()); // class UserDao$$EnhancerByCGLIB$$xxx
proxy.save();
}
}
| 維度 | 靜態代理 | JDK 動態代理 | CGLIB 代理 |
|---|---|---|---|
| 實作方式 | 手寫代理類別 | Proxy.newProxyInstance | Enhancer 建立子類別 |
| 目標類別要求 | 實作介面 | 必須實作介面 | 無需介面(普通類別) |
| 代理型別 | 實作同一介面 | 實作同一介面 | 繼承目標類別 |
| 方法攔截範圍 | 介面所有方法 | 介面所有方法 | 非 final/public 方法 |
| 效能特徵 | 直接呼叫,最快 | 反射呼叫,較慢 | FastClass 機制,較快 |
| 適用場景 | 固定、少量代理 | 介面導向設計 | 無介面類別、高效能需求 |
proxy-target-class="true"proxy-target-class=true(偏好 CGLIB)代理模式是「關注點分離」的經典實踐:將横切關注點(事務、日誌、安全)從業務邏輯中抽離。理解三種代理的底層機制(介面實作 vs 繼承、反射 vs FastClass),能在架構設計與問題排查中做出正確選擇。實務中,JDK 動態代理與CGLIB是主流,靜態代理僅用於簡單固定場景。