五、動態代理
優勢:在靜態代理中目標類很多時候,可以使用動態代理,避免靜態代理的缺點:
動態代理中目標類即使有很多:
1)代理類數量可以很少
2)當你修改了介面中的方法時,不會影響代理類
1、介紹
? 在程式執行程序中,由JVM根據反射機制,創建代理類物件,并動態的指定要代理的目標類,換句話說,動態代理是一種創建Java物件的能力,讓你不需要定義代理類的 .java源檔案(如:Taobao類),就能創建代理類物件,
? 其實,就是jdk運行期間,動態創建class位元組碼并加載到JVM中,
動態:在程式執行時,呼叫 JDK 提供的方法才能創建代理類的物件
【注】:
1)動態代理:使用反射機制,在程式執行中,創建代理類物件;代理的目標類是活動的,可設定的,如,設定不同的目標類,給不同的目標隨時創建代理,
2)靜態代理:代理類時手工實作的Java檔案;代理的目標物件是規定的,
在Java中,要想創建物件:
1)創建類檔案,JVM把檔案編譯為class
2)使用構造方法,創建類的物件
2、實作方法
(1)JDK動態代理:使用Java反射包中的類和介面實作動態代理的功能,
反射包:Java.lang.reflect,主要用到:InvocationHandler、Method、Proxy
(2)Cglib動態代理:Cglib是第三方的工具庫,創建代理物件,
原理:繼承,Cglib通過繼承目標類,創建它的子類,在子類中重寫父類中同名的方法,實作功能的修改,
? 因為Cglib是繼承、重寫方法,所以要求目標類不能是final的,方法也不能是final的,Cglib的要求目標類比較寬松,只要能繼承就可以了,Cglib在很多框架中使用,比如,Mybatis、spring框架中都有使用,
? Cglib(Code Generation Library)是一個開源專案,是一個強大的、高性能的、高質量的Code生產類別庫,它可以在運行期擴展Java類與實作Java介面,廣泛被許多的AOP的框架中使用,如,Spring AOP,
? 使用 JDK 的 Proxy 實作代理,要求目標類與代理類實作相同的介面,若目標類不存在介面,則無法使用該方法實作,
? 對于無介面的類,要為其創建動態代理,就要使用Cglib來實作,Cglib 代理的生成原理是生成目標類的子類,而子類是增強的,這個子類物件就是代理物件,所以,使用 Cglib 生成動態代理,要求目標類必須能夠被繼承,即不能是final類,
3、JDK動態代理
反射包:Java.lang.reflect,主要用到:InvocationHandler、Method、Proxy
(1)InvocationHandler 介面----呼叫處理器【實際上表示你這代理到底要干什么!!】
事件處理,執行目標物件的方法時,會觸發事件處理器方法,會把當前執行的目標物件方法作為引數傳入,
介面中就一個方法 invoke(),表示代理物件要執行的功能代碼,換句話說,代理類要實作的功能(下面列舉的兩種功能)放在invoke() 中
- 代理類完成的功能:
呼叫目標方法,執行目標方法的功能;
功能增強,在目標方法呼叫時,增強功能,
- 代碼:
public interface InvocationHandler {
// proxy JDK創建的代理物件,無需賦值,不需要你管理,沒什么用
// method 目標物件中的方法,JDK提供Method物件,就是要呼叫目標物件的某個方法的Method物件
// args 呼叫目標物件某個方法時接受的引數,JDK提供的,不需要認為參與
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
- 怎么用:
創建類,實作InvocationHandler介面
重寫invoke(),吧原來靜態代理中的代理類要完成的功能寫在這里
(2)Method 類------表示方法的,確切地說是目標類中的方法
作用:通過Method可以執行某個方法類的方法:Method.invoke()
【注】這里的invoke與上面提到的invoke 完全不一樣,只是名字碰巧一樣,
- 上面提及的invoke:介面中的方法;
- 這里提及的invoke:類中的方法,
在動態代理中的使用方法:method.invoke(目標物件,方法的引數args);
【注】可以執行任一物件的方法,而不需要知道這個方法的名稱
說明:method.invoke() 就是用來執行目標方法的,等同于靜態代理中的
float price = factory.sell(amount);
(3)Proxy 類:核心的物件,創建代理物件,之前創建物件都是new類的構造方法(),現在我們是使用proxy類的方法,代替new的使用,
-
方法:靜態方法
newProxyInstance() -
作用是:創建代理物件,等同于靜態代理中的
TaoBao taoBao=new TaoBao() -
代碼:
public class Proxy implements java.io.Serializable {
@CallerSensitive
// 這里會用到反射機制
// ClassLoader loader 當前目標物件使用的類加載器,獲取加載器的方固定:target.getClass().getClassLoader()
// Class<?>[] interfaces 目標物件要實作的介面,用泛型確認型別:target.getClass().getInterfaces(),實際上,這個引數給代理物件提供了一組介面,這個代理物件會實作這些介面,
// InvocationHandler h 我們自己寫的,代理類要完成的功能
// 回傳物件 ---> 代理物件
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{....此處省略}
}
(4)實作動態代理的步驟:
創建介面,定義目標類要完成的功能;
創建目標類實作介面;
創建InvocationHandler介面的實作類,在invoke方法中完成代理類的功能:
- 呼叫目標方法
- 增強功能
使用Proxy類的靜態方法newProxyInstance(),創建代理物件,并把回傳值轉為介面型別,
(5)UML類圖:

(6)代碼:
***UsbSell介面
// 表示功能的廠家,或者商家要完成的功能
public interface UsbSell {
// 定義方法
// amount: 表示一次購買的數量,暫時不用
// 回傳表示一個U盤的價格
float sell(int amount);
// 可以定義多個其他的方法
}
***UsbKingFactory類
// 目標類:金士頓廠家,不接用戶的單獨購買
public class UsbKingFactory implements UsbSell {
@Override
public float sell(int amount) {
// 一個128G的U盤85元
// 后期可以根據amount,可以實作不同的價格,比如1000個,單價85元,
System.out.println("目標類中,執行sell目標方法");
return 85.0f;
}
}
***MySellHandler類
// 必須實作InvocationHandler,完成代理類需要完成的功能(1、待用目標方法;2、功能增強)
public class MySellHandler implements InvocationHandler {
private Object target = null;
// 動態代理:目標物件是活動的,不是固定的,需要傳入進來
// 傳入是誰,就給誰創建代理物件
public MySellHandler(Object target){
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 向廠家發送訂單,告訴廠家,我買了U盤,廠家發貨
//float price = factory.sell(amount);//廠家的價格
Object res = method.invoke(target,args);
// 商家 需要加價,也就是dialing要增加價格
// 這里相對于目標物件,就增強了功能
// 代理類 在完成目標類方法呼叫后,增強了功能
//price += 25;
if(res!=null){
Float price = (Float) res;//強轉
price += 25;
res = price;
}
// 在目標類的方法呼叫后,你做的其他功能,都是增強功能,如發放優惠券
System.out.println("淘寶商家,給你回傳一個優惠券,或者紅包");
return res;
}
}
***MainShop(客戶端)
public class MainShop {
public static void main(String[] args) {
//1.創建物件,使用Proxy
//2.創建目標物件
UsbSell factory = new UsbKingFactory();
//3.創建Invocationhandler物件
InvocationHandler myHandle = new MySellHandler(factory);
//4.創建代理物件
UsbSell proxy = (UsbSell) Proxy.newProxyInstance
(factory.getClass().getClassLoader(), // 通過反射得到目標物件的類加載器
factory.getClass().getInterfaces(), // 通過反射得到目標物件的介面
myHandle); // 執行功能的Handle
// 這里 proxy.getclass().getName() = com.sun.proxy.$Proxy0 ,是可以轉為目標物件所實作的所有介面
System.out.println(proxy.getClass().getName());
// factory.getClass().getInterfaces() 這個引數就說明了 目標物件 UsbKingFactory 有哪些,在創建代理物件的時候,會讓代理物件自動實作這些介面,同時這個代理物件本身也實作Proxy
// 只是這里進行了強轉,轉為 目標物件 UsbKingFactory 實作的介面 UsbSell
//通過代理執行方法
float price = proxy.sell(1);
// 在執行這個 代理物件 執行 目標方法的時候,實際上用的是 myHandle 中的 invoke(),
// sell 方法 賦給 myHandle 中的 invoke 的 引數 method,引數 是 1
System.out.println("通過動態代理物件,呼叫方法:" +price);
}
}
(7)JDK動態代理執行流程


最后,要知道動態代理是做什么的?
? 可以在不改變原來目標方法功能的前提下,可以在代理中增強自己的功能代碼
比如,在專案開發中,有一個功能是其他人(其他部門,其他小組的人)寫好的,就可以使用動態代理,在 別人的基礎上在自己的代理中增強功能,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/388431.html
標籤:其他
