文章目錄
- 代理模式
- 1、使用代理模式的作用
- 2、實作代理的方式
- 3、靜態代理
- 4、動態代理
- 4.1、JDK動態代理
- 4.1.1、什么是JDK動態代理
- 4.1.2、jdk動態代理的實作
- 4.2、CGLIB代理
- 5、靜態代理和動態代理的對比
代理模式
代理模式是指,為其他物件提供一種代理以控制對這個物件的訪問,在某些情況下,一個物件不適合或者不能直接參考另一個物件,而代理物件可以在客戶類和目標物件之間起到中介的作用,
換句話說,使用代理物件,是為了在不修改目標物件的基礎上,增強主業務邏輯,
客戶類真正想要訪問的物件是目標物件,但客戶類真正可以訪問的物件是代理物件,客戶類對目標物件的訪問是通過訪問代理物件來實作的,代理類與目標類要實作同一個介面,
例如:有A,B,C三個類,A原來可以呼叫C類的方法,現在因為某種原因C類不允許A類呼叫其方法,但B類可以呼叫C類的方法,A類通過B類呼叫C類的方法,這里B是C的代理,A通過代理B訪問C,
原來的訪問關系:

通過代理的訪問關系:

比如Windows系統的快捷方式也是一種代理模式,快捷方式代理的是真實的程式,雙擊快捷方式是啟動它代表的程式,
1、使用代理模式的作用
①、功能增強:在原有的功能上,增加了額外的功能,新增加的功能,叫做功能增強,
②、控制訪問:代理類不讓你訪問目標,例如商家不讓用戶訪問廠家類似,
2、實作代理的方式
靜態代理和動態代理,
3、靜態代理
靜態代理是指,代理類在程式運行前就已經定義好.java源檔案,其與目標類的關系在程式運行前就已經確立,在程式運行前代理類已經編譯為.class檔案,
靜態代理實作步驟:
(1)、定義一個介面及其實作類;
(2)、創建一個代理類同時實作這個介面;
(3)、將目標物件注入進代理類,然后在代理類的對應方法呼叫目標類中的對應方法,這樣我們就可以通過代理類屏蔽對目標物件的訪問,并且可以在目標方法執行前后做一些自己想做的事情,
特點:實作簡單、容易理解,
模擬商家在TaoBao和JingDong購買筆記本電腦的例子:
定義業務介面
//定義業務介面 LaptopSell(目標介面)
public interface LaptopSell {
//抽象方法sell, sell是目標方法
float sell(int amount);
}
定義介面實作類:
//LenovoLaptopFactory目標類,該類實作了業務介面
public class LenovoLaptopFactory implements LaptopSell {
@Override
public float sell(int amount) {
return 5550.0f;
}
}
代理商TaoBao
public class TaoBao implements LaptopSell {
//宣告商家代理的廠家是哪個
private LenovoLaptopFactory factory = new LenovoLaptopFactory();
@Override
public float sell(int amount) {
//向廠家發送頂訂單告訴廠家我買了電腦,廠家發貨
//呼叫目標類的方法
float price = factory.sell(amount); //廠家的價格
//商家,需要加價,也就是代理要增加價格
price += 100; //增強功能,代理類在完成目標類方法呼叫后增強了功能
return price;
}
}
代理商JingDong:
public class JingDong implements LaptopSell {
private LenovoLaptopFactory factory = new LenovoLaptopFactory();
@Override
public float sell(int amount) {
//呼叫目標類中的方法
float price = factory.sell(amount);
price += 90;
return price;
}
}
客戶端呼叫者,購買商品類:
public class ShopMain {
public static void main(String[] args) {
float price = 0.0f;
TaoBao taoBao = new TaoBao();
price = taoBao.sell(1);
System.out.println(price);
System.out.println("============");
JingDong jingDong = new JingDong();
price = jingDong.sell(1);
System.out.println(price);
}
}
圖示:

靜態代理的缺點:
1)、代碼復雜,難于管理
代理類和目標類實作了相同的介面,每個代理都需要實作目標類的方法,這樣就出現了大量的代碼重復,如果介面增加一個方法,除了所有目標類需要實作這個方法外,所有代理類也需要實作此方法,增加了代碼維護的復雜度,
2)代理類依賴目標類,代理類過多
代理類只服務于一種型別的目標類,如果要服務多個型別,勢必要為每一種目標類都進行代理,靜態代理在程式規模稍大時就無法勝任了,代理類數量過多,
4、動態代理
動態代理是指代理類物件在程式運行時由JVM根據反射機制動態生成的,動態代理不需要定義代理類的Java源檔案,
動態代理其實就是jdk運行期間,動態創建class位元組碼并加載到JVM,
動態代理的實作方式有兩種:
1、使用JDK動態代理;
2、通過CGLIB動態代理;
4.1、JDK動態代理
4.1.1、什么是JDK動態代理
使用java反射包中的類和介面實作動態代理的功能,
JDK的動態代理要求目標物件必須實作介面
反射包 java.lang.reflect , 里面有三個類 : InvocationHandler , Method, Proxy.
一、InvocationHandler 介面(呼叫處理器):
就一個方法invoke(),
invoke():表示代理物件要執行的功能代碼,你的代理類要完成的功能就寫在invoke()方法中,
代理類完成的功能:
1、 呼叫目標方法,執行目標方法的功能
2、 功能增強,在目標方法呼叫時,增加功能,
方法原型:
引數: Object proxy: jdk創建的代理物件,無需賦值,
Method method: 目標類中的方法,jdk提供method物件的
Object[] args:目標類中方法的引數, jdk提供的,
public Object invoke(Object proxy, Method method, Object[] args)
InvocationHandler 介面:表示你的代理要干什么,
怎么用:
1.創建類實作介面InvocationHandler;
2.重寫invoke()方法, 把原來靜態代理中代理類要完成的功能,寫在這,
二、Method類:
表示方法的, 確切的說就是目標類中的方法,
作用:通過Method可以執行某個目標類的方法,Method.invoke();
method.invoke(目標物件,方法的引數)
說明: method.invoke()就是用來執行目標方法的,等同于靜態代理中的
向廠家發送訂單,告訴廠家,我買了Laptop,廠家發貨
float price = factory.sell(amount);
三、Proxy類:
核心的物件,創建代理物件,之前創建物件都是 new 類的構造方法(),
現在我們是使用Proxy類的方法,代替new的使用,
方法: 靜態方法 newProxyInstance()
作用是: 創建代理物件, 等同于靜態代理中的TaoBao taoBao = new TaoBao();
引數:
- ClassLoader loader 類加載器,負責向記憶體中加載物件的, 使用反射獲取物件的ClassLoader
類a , a.getCalss().getClassLoader(), 目標物件的類加載器 - Class<?>[] interfaces: 介面, 目標物件實作的介面,也是反射獲取的,
- InvocationHandler h : 我們自己寫的,代理類要完成的功能,
回傳值:就是代理物件
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
4.1.2、jdk動態代理的實作
實作步驟:
1、創建介面,定義目標類要完成的功能
2、創建目標類實作介面
3、創建InvocationHandler介面的實作類,在invoke方法中完成代理類的功能
①、呼叫目標方法;
②、增強功能,
4、使用Proxy類的靜態方法,創建代理物件,并把回傳值轉為介面型別,
定義目標介面:
public interface LaptopSell {
float sell(int amount);
}
定義目標介面實作類:
public class LenovoLaptopFactory implements LaptopSell {
@Override
public float sell(int amount) {
return 5550.0f;
}
}
定義呼叫處理程式:
public class MySellHandler implements InvocationHandler {
private Object target = null;
public MySellHandler() {}
//使用構造方法傳入目標物件,給目標物件完成代理功能
public MySellHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//執行目標方法
Object ret = method.invoke(target,args);
float price = (float) ret;
//在目標方法執行完后增強功能
ret = price + 100;
return ret;
}
}
創建動態代理物件:
public class MainShop {
public static void main(String[] args) {
//創建目標物件
LaptopSell factory = new LenovoLaptopFactory();
//創建InvocationHandler物件
InvocationHandler handler = new MySellHandler(factory);
//創建代理物件
LaptopSell proxy = (LaptopSell) Proxy.newProxyInstance(factory.getClass().getClassLoader(),
factory.getClass().getInterfaces(),handler);
//通過代理物件執行目標方法
System.out.println(proxy.sell(1));
}
}
圖示:

類圖:

4.2、CGLIB代理
CGLIB (opens new window)(Code Generation Library)是一個基于ASM (opens new window)的位元組碼生成庫,它允許我們在運行時對位元組碼進行修改和動態生成,CGLIB 通過繼承方式實作代理,很多知名的開源框架都使用到了CGLIB (opens new window), 例如 Spring 中的 AOP 模塊中:如果目標物件實作了介面,則默認采用 JDK 動態代理,否則采用 CGLIB 動態代理,
JDK 動態代理和 CGLIB 動態代理對比:
1、JDK 動態代理只能代理實作了介面的類或者直接代理介面,而 CGLIB 可以代理未實作任何介面的類, 另外, CGLIB 動態代理是通過生成一個被代理類的子類來攔截被代理類的方法呼叫,因此不能代理宣告為 final 型別的類和方法,
2、就二者的效率來說,大部分情況都是 JDK 動態代理更優秀,
5、靜態代理和動態代理的對比
①、動態代理更加靈活,不需要必須實作介面,可以直接代理實作類,并且可以不需要針對每個目標類都創建一個代理類,靜態代理中,介面一旦新增加方法,目標物件和代理物件都要進行修改,非常麻煩,
②、靜態代理在編譯時就將介面、實作類、代理類這些都變成了一個個實際的 class 檔案,而動態代理是在運行時動態生成類位元組碼,并加載到 JVM 中的,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/379536.html
標籤:java
上一篇:LeetCode - 506 - 相對名次 - java
下一篇:一文學會多執行緒
