需求場景 按著慣例,還是以一個應用場景作為代理模式的切入點。現在有一個訂單系統,要求是:一旦訂單被創建,只有訂單的創建人才可以修改訂單中的數據,其他人則不能修改。 基本實現思路 按著最直白的思路,就是查詢資料庫中訂單的創建人和當前Session中的登錄賬號ID是否一致。 class Order { ...
需求場景
按著慣例,還是以一個應用場景作為代理模式的切入點。現在有一個訂單系統,要求是:一旦訂單被創建,只有訂單的創建人才可以修改訂單中的數據,其他人則不能修改。
基本實現思路
按著最直白的思路,就是查詢資料庫中訂單的創建人和當前Session中的登錄賬號ID是否一致。
class Order { private String orderId; private String creatorId; // 訂單創建者的ID private String details; // 訂單詳情 // 省略其他屬性和getter/setter方法 public Order(String orderId, String creatorId, String details) { this.orderId = orderId; this.creatorId = creatorId; this.details = details; } // 其他業務邏輯... }
系統修改
class OrderService { private Map<String, Order> orders = new HashMap<>(); // 創建訂單 public void createOrder(String orderId, String creatorId, String details) { Order order = new Order(orderId, creatorId, details); orders.put(orderId, order); } // 修改訂單 public void modifyOrder(String orderId, String userId, String newDetails) { Order order = orders.get(orderId); if (order != null) { //檢查是否擁有許可權 if(order.getCreateId().equals(userId)){ order.setDetails(newDetails); }else{ System.out.println("許可權不足."); } } else { System.out.println("訂單不存在."); } } // 其他業務邏輯... }
該思路的問題
上述代碼其實本身是沒有問題的,也是Web貧血模式的常見實現思路,即在Service中通過大量的if else進行完成,如果非說問題的話,就是隨著對於訂單的操作越多Service代碼會越發膨脹,例如,需求一開始是只要求改描述,下次又要求更改名稱,下下次對於許可權又細分等等,Service的modifyOrder就會增加很多的if else和set方法 ,擴展和維護十分的不優雅。或許下麵的代理模式能提供一些能夠優雅解決的新思路。
代理模式
代理模式的核心定義是:為其他對象提供一種代理以此來控制對這個對象的訪問。代理模式是以對象組合的方式對對象進行保護或者說功能擴展的一種方式。
代理模式結構
Sunject :目標介面,定義目標對象的具體操作。
Proxy:代理對象,實現與具體的目標對象一樣的介面,這樣就可以代理具體的目標對象。保存一個指向具體目標對象的引用,可以再需要的時候調用具體的目標對象,調用目標對象時進行控制和保護。
RealSubject:具體的目標對象,真正實現目標介面要求的功能
// 定義真實主題角色介面 interface Image { void display(); } // 實現真實主題角色 class RealImage implements Image { private String fileName; public RealImage(String fileName) { this.fileName = fileName; loadFromDisk(fileName); } @Override public void display() { System.out.println("Displaying " + fileName); } // 模擬從磁碟載入圖片資源 private void loadFromDisk(String fileName) { System.out.println("Loading " + fileName + " from disk."); } } // 定義代理主題角色 interface Proxy extends Image { void display(); } // 實現代理主題角色 class ProxyImage implements Proxy { private RealImage realImage; public ProxyImage(String fileName) { // 延遲載入RealImage對象 this.realImage = null; } @Override public void display() { if (realImage == null) { realImage = new RealImage("image.png"); } realImage.display(); } } public class ProxyPatternDemo { public static void main(String[] args) { Proxy proxy = new ProxyImage("image.png"); proxy.display(); } }
代理模式實現案例
相當於現在如果有了一個訂單對象實例,那麼就需要控制外部對它的訪問,滿足條件的可以訪問,不滿足條件的就不能訪問。使用代理模式來實現就是需要在Order對象之外再包一層對象,用於操作許可權控制。本質上是一種保護代理思路。
首先創建一個訂單的操作介面
public interface OrderApi { String getId(); String getName(); String getDetails(); String getCreatorId(); void setId(String id); void setDetails(String details); void setName(String name); void setCreatorId(String creatorId); }
一個基本的訂單實體類作為目標代理對象
class Order implements OrderApi { private String id; private String name; private String details; private String creatorId; public Order(String id, String name, String details, String creatorId) { this.id = id; this.name = name; this.details = details; this.creatorId = creatorId; } @Override public String getId() { return id; } @Override public String getName() { return name; } @Override public String getDetails() { return details; } @Override public String getCreatorId() { return creatorId; } @Override public void setId(String id) { this.id = id; } @Override public void setName(String name) { this.name = name; } @Override public void setCreatorId(String creatorId) { this.creatorId = creatorId; } @Override public void setDetails(String details) { this.details= details; } }
實現一個代理對象
class OrderProxy implements OrderApi { private Order order; public OrderProxy(Order order) { this.order = order; } @Override public String getId() { return order.getIdO(); } @Override public String getName() { return order.getNameO(); } @Override public String getDetails() { return order.getDetailsO(); } @Override public String getCreatorId() { return order.getCreatorIdO(); } @Override public void setId(String id) { // 在這裡添加許可權檢查邏輯 if (isCreator()) { order.setId(id); } else { throw new SecurityException("Only the creator can change the order ID."); } } @Override public void setName(String name) { // 在這裡添加許可權檢查邏輯 if (isCreator()) { order.setName(name); } else { throw new SecurityException("Only the creator can change the order name."); } } @Override public void setCreatorId(String creatorId) { // 創建者ID通常不允許更改 throw new UnsupportedOperationException("Changing creator ID is not allowed."); } private boolean isCreator(String userId) { // 這裡應該添加檢查userId是否是訂單的創建者 // 為了示例簡單,這裡假設userId總是傳入正確的,返回true return true; } }
代理模式的理解
特點與分類
代理模式在客戶和被客戶訪問的對象之間,引入了一定程度的間接性,客戶是直接使用代理,讓代理來與被訪問的對象進行交互。不同的代理類型,這種附加的間接性有不同的用途,也就具有不同的特點。
- 遠程代理:隱藏了一個對象存在於不同的地址空間的事實,也即是客戶通過遠程代理去訪問一個對象,根本就不關心這個對象在哪裡,也不關心如何通過網路去訪問到這個對象。從客戶的角度來講,它只是在使用代理對象而已。
- 虛代理:可以根據需要來創建“大”對象,只有到必須創建對象的時候,虛代理才會創建對象,從而大大加快程式運行速度,並節省資源。通過虛代理可以對系統進行優化。
- 保護代理:可以在訪問一個對象的前後,執行很多附加的操作,除了進行許可權控制之外,還可以進行很多跟業務相關的處理,而不需要修改被代理的對象。也就是說,可以通過代理來給目標對象增加功能。
- 智能指引:和保護代理類似,也是允許在訪問一個對象的前後,執行很多附加的操作,這樣一來就可以做很多額外的事情,比如,引用計數等。
在這些代理類型中,最常見的是保護代理和遠程代理。上述的例子就是一個典型的保護代理的實現,即具體訂單的操作是不變的,如果需要對訂單的操作進行特殊處理,一切變動皆集中在代理對象中,代理對象對於訂單對象起到了保護隔離的作用,同時代碼層面上也承載了“頻繁變化”的需求內容,將“變化”隔離出來,對於後續的需求擴展也是十分有效。
建議在如下情況中選用代理模式。
- 需要為一個對象在不同的地址空間提供局部代表的時候,可以使用遠程代理。
- 需要按照需要創建開銷很大的對象的時候,可以使用虛代理。
- 需要控制對原始對象的訪問的時候,可以使用保護代理。
- 需要在訪問對象執行一些附加操作的時候,可以使用智能指引代理。
具體目標和代理間的關係
Java中代理模式的應用
Java 對代理模式提供了內建的支持,在java.lang,refect 包下麵,提供了一個 Proxy的類和一個InvocationHandler 的介面。
通常把前面自己實現的代理模式稱為 Java 的靜態代理。這種實現方式有一個較大的缺點,就是如果Subject介面發生變化,那麼代理類和具體的目標實現都要變化,不是很靈活。而使用Java內建的對代理模式支持的功能來實現則沒有這個問題。
通常把使用 Java 內建的對代理模式支持的功能來實現的代理稱為Java的動態代理,動態代理跟靜態代理相比,明顯的變化是:靜態代理實現的時候,在Subject介面上定義很多的方法,代理類裡面自然也要實現很多方法:而動態代理實現的時候,雖然Subicct介面上定義了很多方法,但是動態代理類始終只有一個invoke 方法。這樣,當Subject介面發生變化的時候,動態代理的介面就不需要跟著變化了。
Java的動態代理目前只能代理介面,基本的實現是依靠Java的反射機制和動態生成class的技術,來動態生成被代理的介面的實現對象。具體的內部實現細節這裡不去討論。如果要實現類的代理,可以使用cglib(一個開源的Code Generation Library)。
還是來看看示例,那就修改上面保護代理的示例,看看如何使用Java的動態代理來實現同樣的功能。
(1)訂單介面的定義是完全一樣的,就不再贅述了。
(2)訂單對象的實現,只是添加了一個 toString,,以方便測試輸出,這裡也不去示例了。在前面的示例中,toString已實現在代理類裡面了。
(3)直接看看代理類的實現,大致有如下變化。
- 要實現InvocationHandler介面。
- 需要提供一個方法來實現:把具體的目標對象和動態代理綁定起來,併在綁定好過後,返回被代理的目標對象的介面,以利於客戶端的操作。
- 需要實現 invoke 方法,在這個方法裡面,具體判斷當前是在調用什麼方法,需要如何處理
import java.lang.reflect.*; /** * 使用Java中的動態代理 */ public class DynamicProxy implements InvocationHandler { // 被代理的對象 private OrderApi order; /** * 獲取綁定好代理和具體目標對象後的目標對象的介面 * @param order 具體的訂單對象,相當於具體目標對象 * @return 綁定好代理和具體目標對象後的目標對象的介面 */ public OrderApi getProxyInterface(Order order) { // 設置被代理的對象,方便invoke裡面的操作 this.order = order; // 把真正的訂單對象和動態代理關聯起來 OrderApi orderApi = (OrderApi) Proxy.newProxyInstance( order.getClass().getClassLoader(), order.getClass().getInterfaces(), this); return orderApi; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 如果是調用setter方法就需要檢查許可權 if (method.getName().startsWith("set")) { // 假設Order類有一個getOrderUser()方法來獲取訂單創建者的用戶ID // 並且order.getOrderUser().equals(args[0])來檢查是否是創建者 if (order.getOrderUser() != null && order.getOrderUser().equals(args[0])) { // 可以操作 return method.invoke(order, args); } else { // 如果不是創建者,不能修改 System.out.println("對不起," + args[0] + ",您無權修改本訂單中的數據"); } } else { // 不是調用的setter方法就繼續運行 return method.invoke(order, args); } return null; } }
使用規則
public class Client { public static void main(String[] args) { // 張三先登錄系統創建了一個訂單 Order order = new Order("XXX", 100, "張三"); // 創建一個動態代理 DynamicProxy dynamicProxy = new DynamicProxy(); // 然後把訂單和動態代理關聯起來 OrderApi orderApi = dynamicProxy.getProxyInterface(order); // 以下就需要使用被代理過的介面來操作了 // 李四想要來修改,那就會報錯 orderApi.setOrderNum(123, "李四"); // 輸出order System.out.println("李四修改後訂單記錄沒有變化:" + orderApi); // 張三修改就不會有問題 orderApi.setOrderNum(123, "張三"); // 再次輸出order System.out.println("張三修改後,訂單記錄:" + orderApi); } }
代理在Java中的使用十分常見,例如Spring中的AOP,其本質就是代理模式