代理模式為某物件提供一個替身(代理),控制對原物件的存取。生活中無處不在:火車票代售點(代理火車站賣票)、VPN 翻牆(代理存取受限網站)、資料庫連線池(代理 Connection.close 歸還而非關閉)。
手寫代理類,適用於介面固定、方法較少的場景。
// 1. 抽象主題
public interface Image {
void display();
}
// 2. 真實主題:載入大圖耗時
public class RealImage implements Image {
private String filename;
public RealImage(String filename) {
this.filename = filename;
loadFromDisk(); // 耗時操作
}
private void loadFromDisk() { System.out.println("載入圖片:" + filename); }
@Override public void display() { System.out.println("顯示:" + filename); }
}
// 3. 靜態代理:首次 display 才載入
public class ProxyImage implements Image {
private RealImage realImage;
private String filename;
public ProxyImage(String filename) { this.filename = filename; }
@Override
public void display() {
if (realImage == null) realImage = new RealImage(filename); // 惰性載入
realImage.display();
}
}
// 4. 客戶端
public class Client {
public static void main(String[] args) {
Image img = new ProxyImage("huge_photo.jpg");
System.out.println("代理建立完成,尚未載入圖片");
img.display(); // 此時才真正載入
img.display(); // 第二次直接使用快取
}
}
核心類:java.lang.reflect.Proxy、InvocationHandler。前提:被代理類必須實作介面。
public static Object newProxyInstance(
ClassLoader loader, // 類載入器
Class>[] interfaces, // 代理要實作的介面陣列
InvocationHandler h // 方法攔截處理器
)
// 1. 定義業務介面
public interface UserService {
void addUser(String name);
String getUser(int id);
void deleteUser(int id);
}
// 2. 真實實作
public class UserServiceImpl implements UserService {
@Override public void addUser(String name) {
System.out.println("新增用戶:" + name);
}
@Override public String getUser(int id) {
return "User_" + id;
}
@Override public void deleteUser(int id) {
System.out.println("刪除用戶:" + id);
}
}
// 3. 通用攔截器:橫切關注點
public class LoggingHandler implements InvocationHandler {
private final Object target;
public LoggingHandler(Object target) { this.target = target; }
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long start = System.nanoTime();
System.out.println("▶ 開始執行:" + method.getName());
Object result = method.invoke(target, args); // 反射呼叫真實方法
long cost = System.nanoTime() - start;
System.out.println("■ 結束執行:" + method.getName() + " 耗時 " + cost/1_000_000 + " ms");
return result;
}
}
// 4. 客戶端使用
public class DynamicProxyDemo {
public static void main(String[] args) {
UserService real = new UserServiceImpl();
UserService proxy = (UserService) Proxy.newProxyInstance(
UserService.class.getClassLoader(),
new Class[]{UserService.class},
new LoggingHandler(real)
);
proxy.addUser("Alice");
System.out.println("查詢結果:" + proxy.getUser(101));
proxy.deleteUser(101);
}
}
Proxy.newProxyInstance → getProxyClass0 獲取/生成代理 ClassProxyGenerator.generateProxyClass 依介面動態產生 class $Proxy0 extends Proxy implements YourInterfaceInvocationHandler.invoke()<clinit>) 緩存 Method 物件供 invoke 使用原理:繼承目標類並覆寫非 final 方法。適用於沒有實作介面的類或需要代理具體類的場景(Spring AOP 預設策略)。
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
public class UserServiceCglib { // 無介面的具體類
public void addUser(String name) { System.out.println("CGLIB 新增:" + name); }
public String getUser(int id) { return "CGLIB_User_" + id; }
}
public class CglibProxyDemo {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserServiceCglib.class);
enhancer.setCallback((MethodInterceptor) (obj, method, args1, proxy) -> {
System.out.println("【CGLIB】前置增強:" + method.getName());
Object result = proxy.invokeSuper(obj, args1); // 呼叫父類方法
System.out.println("【CGLIB】後置增強:" + method.getName());
return result;
});
UserServiceCglib proxy = (UserServiceCglib) enhancer.create();
proxy.addUser("Bob");
System.out.println(proxy.getUser(202));
}
}
| 維度 | 靜態代理 | JDK 動態代理 | CGLIB 代理 |
|---|---|---|---|
| 需實作介面 | 是 | 強制 | 否(繼承類) |
| 代理 final 方法 | 可控制 | 不可(介面無 final) | 不可(無法覆寫) |
| 生成時機 | 編譯期 | 運行期 | 運行期 |
| 效能 | 最佳(直接呼叫) | 反射呼叫有開銷 | FastClass 索引較快 |
| 適用場景 | 固定介面、少量方法 | 介面導向、AOP 切面 | 無介面類、第三方庫擴充 |
| Spring 預設 | - | 有介面時 | 無介面時 |
代理模式是「關注點分離」的利器,將核心業務與橫切邏輯(日誌、事務、權限、快取)解耦。掌握 JDK 動態代理與 CGLIB 的原理差異,能讓你在框架選型、效能調優、疑難排查時遊刃有餘。