代理模式基礎概念
代理模式是一種結構型設計模式,其核心思想是通過創建一個代理對象來控制對另一個真實對象的訪問。代理對象在客戶端和真實對象之間起到中介作用,允許在不改變真實對象的前提下,對其進行增強或控制。
代理模式的核心組件
- 主題接口 (Subject)?- 定義真實對象和代理對象的共同接口,客戶端通過該接口訪問真實對象
- 真實主題 (RealSubject)?- 實現主題接口,是實際要被代理的對象
- 代理 (Proxy)?- 實現主題接口,持有真實主題的引用,在調用真實主題方法前后可以添加額外邏輯
靜態代理實現
靜態代理是指在編譯時就已經確定代理類和被代理類的關系,代理類需要手動編寫。
// 主題接口
interface Image {void display();void resize();
}// 真實主題
class RealImage implements Image {private String fileName;public RealImage(String fileName) {this.fileName = fileName;loadFromDisk(fileName);}@Overridepublic void display() {System.out.println("Displaying " + fileName);}@Overridepublic void resize() {System.out.println("Resizing " + fileName);}private void loadFromDisk(String fileName) {System.out.println("Loading " + fileName);}
}// 代理類
class ProxyImage implements Image {private RealImage realImage;private String fileName;public ProxyImage(String fileName) {this.fileName = fileName;}@Overridepublic void display() {// 延遲加載:在需要顯示時才創建真實對象if (realImage == null) {realImage = new RealImage(fileName);}// 可以在調用真實對象方法前后添加額外邏輯System.out.println("Before displaying");realImage.display();System.out.println("After displaying");}@Overridepublic void resize() {if (realImage == null) {realImage = new RealImage(fileName);}System.out.println("Before resizing");realImage.resize();System.out.println("After resizing");}
}// 客戶端代碼
public class StaticProxyClient {public static void main(String[] args) {Image image = new ProxyImage("test.jpg");// 第一次調用display,會加載圖片并顯示System.out.println("First call to display:");image.display();// 第二次調用display,不會重新加載圖片System.out.println("\nSecond call to display:");image.display();// 調用resize方法System.out.println("\nCall to resize:");image.resize();}
}
動態代理實現
動態代理是指在運行時通過反射機制動態生成代理類,無需手動編寫。Java 提供了java.lang.reflect.Proxy
和java.lang.reflect.InvocationHandler
來實現動態代理。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;// 主題接口
interface UserService {void createUser(String username);void deleteUser(int userId);String getUser(int userId);
}// 真實主題
class UserServiceImpl implements UserService {@Overridepublic void createUser(String username) {System.out.println("Creating user: " + username);}@Overridepublic void deleteUser(int userId) {System.out.println("Deleting user with ID: " + userId);}@Overridepublic String getUser(int userId) {System.out.println("Getting user with ID: " + userId);return "User" + userId;}
}// 動態代理處理器
class LoggingHandler implements InvocationHandler {private final Object target; // 真實對象public LoggingHandler(Object target) {this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 方法調用前的日志記錄System.out.println("Before method: " + method.getName());if (args != null) {for (int i = 0; i < args.length; i++) {System.out.println(" Arg " + (i + 1) + ": " + args[i]);}}// 調用真實對象的方法Object result = method.invoke(target, args);// 方法調用后的日志記錄System.out.println("After method: " + method.getName());return result;}
}// 客戶端代碼
public class DynamicProxyClient {public static void main(String[] args) {// 創建真實對象UserService userService = new UserServiceImpl();// 創建InvocationHandlerInvocationHandler handler = new LoggingHandler(userService);// 創建動態代理對象UserService proxyService = (UserService) Proxy.newProxyInstance(UserService.class.getClassLoader(),new Class<?>[]{UserService.class},handler);// 調用代理對象的方法proxyService.createUser("John Doe");System.out.println();String user = proxyService.getUser(123);System.out.println("Returned user: " + user);System.out.println();proxyService.deleteUser(123);}
}
CGLIB 代理實現
CGLIB 是一個強大的、高性能的代碼生成庫,可以在運行時擴展 Java 類與實現 Java 接口。當需要代理沒有實現接口的類時,可以使用 CGLIB 代理。
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;import java.lang.reflect.Method;// 沒有實現接口的目標類
class CustomerService {public void saveCustomer(String name) {System.out.println("Saving customer: " + name);}public String getCustomer(int id) {System.out.println("Getting customer with ID: " + id);return "Customer" + id;}
}// CGLIB方法攔截器
class CustomerServiceInterceptor implements MethodInterceptor {@Overridepublic Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {System.out.println("Before method: " + method.getName());// 調用父類(即目標類)的方法Object result = proxy.invokeSuper(obj, args);System.out.println("After method: " + method.getName());return result;}
}// 客戶端代碼
public class CglibProxyClient {public static void main(String[] args) {// 創建Enhancer對象Enhancer enhancer = new Enhancer();// 設置要代理的目標類enhancer.setSuperclass(CustomerService.class);// 設置回調函數enhancer.setCallback(new CustomerServiceInterceptor());// 創建代理對象CustomerService proxy = (CustomerService) enhancer.create();// 調用代理對象的方法proxy.saveCustomer("Alice");System.out.println();String customer = proxy.getCustomer(456);System.out.println("Returned customer: " + customer);}
}
代理模式的應用場景
- 遠程代理?- 為一個位于不同地址空間的對象提供本地代理
- 虛擬代理?- 延遲創建開銷大的對象,如圖片懶加載
- 保護代理?- 控制對原始對象的訪問,用于權限管理
- 緩存代理?- 緩存對資源的訪問結果,提高性能
- 智能引用代理?- 在訪問對象時附加額外操作,如引用計數
靜態代理與動態代理的對比
特性 | 靜態代理 | 動態代理 |
---|---|---|
代理類創建時間 | 編譯時 | 運行時 |
代碼復雜度 | 高,需手動編寫每個代理類 | 低,通過反射動態生成 |
可維護性 | 低,接口修改時需同步修改代理類 | 高,自動適配接口變化 |
靈活性 | 低,只能代理特定接口或類 | 高,可以代理任意實現接口的類 |
性能 | 稍高,無需反射 | 稍低,依賴反射調用 |
適用場景 | 簡單場景,代理類數量少 | 復雜場景,需要靈活的代理機制 |
代理模式的優缺點
優點:
- 降低耦合度 - 客戶端與真實對象解耦
- 增強擴展性 - 可以在不修改真實對象的情況下增強功能
- 控制訪問 - 可以對真實對象的訪問進行控制
- 提高性能 - 通過緩存等機制提升性能
缺點:
- 增加系統復雜度 - 引入代理對象,可能使系統變得復雜
- 性能開銷 - 動態代理使用反射,可能帶來性能損失
- 調試困難 - 代理邏輯可能分散在多個地方,增加調試難度
使用代理模式的注意事項
- 合理選擇代理類型?- 根據需求選擇靜態代理、動態代理或 CGLIB 代理
- 接口設計?- 設計良好的主題接口,確保代理和真實對象行為一致
- 代理鏈?- 可以組合多個代理形成代理鏈,實現更復雜的功能
- 性能考慮?- 對于性能敏感的應用,需謹慎使用動態代理
- 兼容性?- CGLIB 代理不能用于 final 類或方法,因為無法被繼承
代理模式是一種非常實用的設計模式,它通過引入代理對象來控制對真實對象的訪問,提供了增強功能、控制訪問和提高性能的能力。在實際開發中,動態代理和 CGLIB 代理更為常用,它們提供了更靈活的代理機制。