一、前期回顧
上一篇文章《JAVA設計模式之模板方法模式和建造者模式》談到了設計模式中建造類的模式,我們來回顧下。模板方法模式定義了核心的演算法結構,然後子類可以實現某些特定結構的演算法內容,建造者模式完全把核心的演算法內容交給子類去實現。我們這篇博文來學習下最常用的設計模式,代理模式。
二、代理模式的定義與實踐
定義:Provide a surrogate or placeholder for another object to control access to it.
翻譯:為其他物件提供一種代理以控制對這個物件的訪問。
代理模式還是比較好理解,就是委託別人做事情,比如我們要租房子,委託中介去找房子,這就是代理模式。代理模式分為靜態代理模式和動態代理模式,我們來結合程式碼看看代理模式如何實現的。
2.1 靜態代理
/***
定義消費者介面,查詢指定區域,指定價格以下的房子*/
public interface ICustomer {
/**查詢房子,指定區域,指定價格*/
String findHouse(String location,int price);
}
/**真正的找房消費者,所以只關注找到這個目標**/
public class YungCustomer implements ICustomer {
@Override
public String findHouse(String location,int price) {
System.out.println("找到了位於["+location+"],價格"+price+"以下的房子");
return "找到了位於["+location+"],價格"+price+"以下的房子";
}
}
/**找房子中介類,找到合適房子再反饋給指消費者**/
public class AgencyProxy implements ICustomer{
private ICustomer coustomer;
public AgencyProxy(ICustomer coustomer) {
this.coustomer = coustomer;
}
@Override
public String findHouse(String location,int price) {
String result="沒有符合條件的房子";
before();
if (null != coustomer){
result= coustomer.findHouse(location,price);
}
after();
return result;
}
private void before(){
System.out.println("開始找房子");
}
private void after(){
System.out.println("結束找房子");
}
}
/***場景類*/
public class Client {
public static void main(String[] args) {
ICustomer customer = new YungCustomer();
ICustomer proxy = new AgencyProxy(customer);
String result = proxy.findHouse("釣魚島附近", 2000);
System.out.println(result);
}
}
複製程式碼
輸出結果:
開始找房子
找到了位於[釣魚島附近],價格2000以下的房子
結束找房子
複製程式碼
靜態代理模式比較簡單,總結就是代理類中包含實際呼叫者的引用,當符合條件時候,在去呼叫真正的物件方法。
2.2 JDK動態代理
動態代理比較複雜一點,相對於靜態代理的指定代理物件,動態代理是在執行時候才知道實際代理物件。動態代理應用比較廣泛,我們用的最多的框架Spring中 AOP就採用了動態代理。我們來把上面的程式碼改造成動態代理。
/**動態代理handler**/
public class MyInvocationHandler implements InvocationHandler {
private Object object;
public MyInvocationHandler(Object object) {
this.object = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
Object result= method.invoke(object,args);
after();
return result;
}
private void before(){
System.out.println("開始找房子");
}
private void after(){
System.out.println("結束找房子");
}
}
public class Client {
public static void main(String[] args) {
// 獲取動態代理物件。
ICustomer customer = (ICustomer) Proxy.newProxyInstance(Client.class.getClassLoader()
, new Class[]{ICustomer.class}
, new MyInvocationHandler(new YungCustomer()));
customer.findHouse("釣魚島附近", 2000);
}
}
複製程式碼
輸出結果:
開始找房子
找到了位於[釣魚島附近],價格2000以下的房子
結束找房子
複製程式碼
動態代理要求代理的類必須要有介面,同時實現InvocationHandler的介面,實現介面的invoke方法即可,在invoke方法內可以在真正執行方法的前後新增想做的事情,比如說記錄日誌,通知訊息等等,這就是AOP的思想,在不改變原有業務程式碼的情況下,新增功能。但是jdk動態代理的缺點就是代理的類必須要有介面才行,為了彌補這個缺點,出現了一款不需要介面就能被代理的第三方庫,CGLIB庫。
2.3.CGLIB動態代理
2.3.1 CGLIB介紹
CGLIB是一個強大的、高效能的程式碼生成庫。它被廣泛使用在基於代理的AOP框架(例如Spring AOP和dynaop)提供方法攔截。在實現內部,CGLIB庫使用了ASM這一個輕量但高效能的位元組碼操作框架來轉化位元組碼,產生新類。
2.3.2 CGLIB的使用
既然是第三方庫,我們需要新增maven依賴
<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.9</version>
</dependency>
複製程式碼
CGLIB也和jdk動態代理一樣,需要設定回撥方法,在JDK動態代理中我們要實現
InvocationHandler,而在CGLIB中我們需要實現net.sf.cglib.proxy.MethodInterceptor
介面作為回撥介面。我們先看程式碼
/**實現回撥介面**/
public class MyMthondInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
before();
Object result = methodProxy.invokeSuper(o,objects);
after();
return result;
}
private void before(){
System.out.println("開始找房子");
}
private void after(){
System.out.println("結束找房子");
}
}
public class Client {
public static void main(String[] args) {
//建立回撥例項
MyMthondInterceptor interceptor=new MyMthondInterceptor();
//CGLIB建立例項
Enhancer enhancer = new Enhancer();
//設定需要代理的類
enhancer.setSuperclass(YungCustomer.class);
//設定回撥類
enhancer.setCallback(interceptor);
//獲取代理物件
YungCustomer customer= (YungCustomer) enhancer.create();
//執行方法
customer.findHouse("釣魚島",1000);
}
}
複製程式碼
輸出結果:
開始找房子
找到了位於[釣魚島],價格1000以下的房子
結束找房子
複製程式碼
這裡CGLIB實現結果和JDK動態代理完全一樣。上面實現回撥方法的時候需要注意,推薦使用methodProxy.invokeSuper(o,objects);
,這裡呼叫的是CJLIB庫的方法,如果使用method.invoke(o,args);
需要注意的是,這裡使用的就是JDK的動態代理了,同時invoke的object必須是傳入的代理例項,而不是方法中形參object,否則會導致死迴圈呼叫。同時考慮到效能,還是建議使用第一種呼叫方式。
CGLIB庫還提供了很多其他的特性,比如回撥方法過濾等等。有興趣的同學可以自行研究。
三、代理模式的優點與缺點
優點
1.職責清晰
真實的角色實現實際的業務邏輯,不用關心其他非核心的業務邏輯。業務是業務,輔助功能是輔助功能,職責非常清晰。比如要實現日誌功能,不用耦合在實際的業務程式碼中,只要做一個代理即可。
2.良好的擴充套件性
由於核心的業務邏輯已經封裝好了,後面要增強業務功能,也可以使用代理模式代理增加功能即可。
缺點
1.類的臃腫
對於靜態代理來說,如果過多的使用靜態代理會帶來類臃腫。一般會在介面協議轉換中使用比較多代理模式。 2.複雜性 對於動態代理來說,設計到回撥方法的實現,特別是CGLIB中的使用,還是帶來了一定的複雜性。
3.效能
對於動態代理來說,都是執行期進行位元組碼操作,所以還是帶來了一些效能損耗,但是這不能作為不使用動態代理的理由,任何東西都是有兩面性的。
四、代理模式的使用場景
1.方法增強;比如增加日誌,事務等功能。
2.遠端RPC呼叫;現在很多分散式系統RPC呼叫都是採用了代理模式。
3.協議等的轉換;比如需要適用另外一套協議介面,會使用代理模式,先轉換為老協議,然後再呼叫實際的類去執行。
4.懶載入;有些框架會在開始的時候使用代理類來替換實際類,等到真正要使用該類的時候才進行載入,從而達到了懶載入的效果。
五、總結
本篇部落格介紹了代理模式,代理模式分為靜態代理和動態代理,動態代理又分為jdk動態代理和CGLIB動態代理。JDK動態代理必須要有介面才能使用,CGLIB彌補了這個缺陷,可以直接對類進行代理。同時CGLIB動態代理效能相對於JDK動態代理要優秀。
六、參考
《設計模式之禪》