1. 程式人生 > >Java動態代理(AOP)

Java動態代理(AOP)

[TOC] 動態代理(理解): 基於反射機制。 現在需要知道以下的就行: 1. 什麼是動態代理 ? 使用jdk的反射機制,建立物件的能力, 建立的是代理類的物件。 而不用你建立類檔案。不用寫java檔案。 動態:在程式執行時,呼叫jdk提供的方法才能建立代理類的物件。 jdk動態代理,必須有介面,目標類必須實現介面, 沒有介面時,需要使用cglib動態代理 1. 知道動態代理能做什麼 ? 可以在不改變原來目標方法功能的前提下, 可以在代理中增強自己的功能程式碼。 程式開發中的意思。 比如:你所在的專案中,有一個功能是其他人(公司的其它部門,其它小組的人)寫好的,你可以使用。 GoNong.class , GoNong gn = new GoNong(), gn.print(); 你發現這個功能,現在還缺點, 不能完全滿足我專案的需要。 我需要在gn.print()執行後,需要自己在增加程式碼。 用代理實現 gn.print()呼叫時, 增加自己程式碼, 而不用去改原來的 GoNong檔案 後面會有,mybatis ,spring ## 一、代理 ### 1. 什麼是代理? 代購, 中介,換ip,商家等等 比如有一家美國的大學, 可以對全世界招生。 留學中介(代理) 留學中介(代理): 幫助這家美國的學校招生, 中介是學校的代理, 中介是代替學校完成招生功能。 代理特點: 1. 中介和代理他們要做的事情是一致的: 招生。 2. 中介是學校代理, 學校是目標。 3. 家長---中介(學校介紹,辦入學手續)----美國學校。 4. 中介是代理,不能白乾活,需要收取費用。 5. 代理不讓你訪問到目標。 或者是買東西都是商家賣, 商家是某個商品的代理, 你個人買東西, 肯定不會讓你接觸到廠家的。 在開發中也會有這樣的情況, 你有a類, 本來是呼叫c類的方法, 完成某個功能。 但是c不讓a呼叫。 a -----不能呼叫 c的方法。 在a 和 c 直接 建立一個 b 代理, c讓b訪問。 a --訪問b---訪問c ### 2. 使用代理模式的作用 1. 功能增強: 在你原有的功能上,增加了額外的功能。 新增加的功能,叫做功能增強。 2. 控制訪問: 代理類不讓你訪問目標,例如商家不讓使用者訪問廠家。 ### 3. 實現代理的方式 靜態代理和動態代理 ## 二、靜態代理 靜態代理是指,代理類在程式執行前就已經定義好.java 原始檔,其與目標類的關係在 程式執行前就已經確立。在程式執行前代理類已經編譯為.class 檔案。 - 代理類是自己手工實現的,自己建立一個java類,表示代理類。 - 同時你所要代理的目標類是確定的。 - 特點: 實現簡單 、容易理解 **缺點:** 當你的專案中,目標類和代理類很多時候,有以下的缺點: - 當目標類增加了, 代理類可能也需要成倍的增加。 代理類數量過多。 - 當你的介面中功能增加了, 或者修改了,會影響眾多的實現類,廠家類,代理都需要修改。 ### 1. 模擬使用者購買u盤 使用者是客戶端類 - 商家:代理,代理某個品牌的u盤。 - 廠家:目標類。 - 三者的關係: 使用者(客戶端)---商家(代理)---廠家(目標) - 商家和廠家都是賣u盤的,他們完成的功能是一致的,都是賣u盤。 **實現步驟:** 1. 建立一個介面,定義賣u盤的方法, 表示你的廠家和商家做的事情 ```java package com.md.service; /** * @author MD * @create 2020-08-03 9:06 */ // 表示功能,廠家和商家都要完成的功能 public interface UsbSell { // 定義方法,返回值為u盤的價格 float sell(int amount); } ``` 1. 創建廠家類,實現1步驟的介面 ```java package com.md.factory; import com.md.service.UsbSell; /** * @author MD * @create 2020-08-03 9:11 */ // 目標類。金士頓廠家 public class UsbKingFactory implements UsbSell { @Override public float sell(int amount) { return 50.0f; } } ``` 1. 建立商家,就是代理,也需要實現1步驟中的介面。 ```java package com.md.shangjia; import com.md.factory.UsbKingFactory; import com.md.service.UsbSell; import java.lang.reflect.AccessibleObject; /** * @author MD * @create 2020-08-03 9:13 */ // 代理類,這是商家,代理金士頓u盤銷售 public class Taobao implements UsbSell { // 訪問目標類 // 宣告商家代理的廠家是誰 private UsbSell factory = new UsbKingFactory(); // 實現銷售u盤的功能 @Override public float sell(int amount) { // 向廠家傳送訂單,告訴廠家,進行發貨,這是進貨價格 float price = factory.sell(amount); // 商家加價獲得利潤,增強功能 price+=50; // 在目標類的方法呼叫之後,剩下寫的其他功能,都是增加功能 System.out.println("淘寶商家給你一個大的優惠卷"); return price; } } ``` 1. 建立商家,不同的商家銷售同一款產品,也是代理,也需要實現1步驟中的介面。 ```java package com.md.shangjia; import com.md.factory.UsbKingFactory; import com.md.service.UsbSell; /** * @author MD * @create 2020-08-03 9:32 */ // 代理類 public class Weishang implements UsbSell { // 代理的也是金士頓 private UsbSell factory = new UsbKingFactory(); @Override public float sell(int amount) { float price = factory.sell(amount); // 增強功能,每個代理根據自己的模式有不同的增強功能 price+=30; System.out.println("微商:親,要五星好評喲!"); return price; } } ``` 1. 建立客戶端類,呼叫商家的方法買一個u盤。 ```java package com.md; import com.md.shangjia.Taobao; import com.md.shangjia.Weishang; /** * @author MD * @create 2020-08-03 9:23 */ public class Shopping { public static void main(String[] args) { // 建立代理的商家物件 Taobao taobao = new Taobao(); float price = taobao.sell(1); System.out.println("通過淘寶購買的u盤:"+price); Weishang weishang = new Weishang(); float sell = weishang.sell(1); System.out.println("通過微商購買的u盤:"+sell); } } ``` ### 2. 靜態代理的缺點 1. 程式碼複雜,難於管理 代理類和目標類實現了相同的介面,每個代理都需要實現目標類的方法,這樣就出現了大量的代 碼重複。如果介面增加一個方法,除了所有目標類需要實現這個方法外,所有代理類也需要實現 此方法。增加了程式碼維護的複雜度。 2. 代理類依賴目標類,代理類過多 代理類只服務於一種型別的目標類,如果要服務多個型別。勢必要為每一種目標類都進行代理, 靜態代理在程式規模稍大時就無法勝任了,代理類數量過多。 ## 三、動態代理 動態代理是指代理類物件在程式執行時由 JVM 根據反射機制動態生成的。動態代理不需要定義代理類的.java 原始檔。 動態代理其實就是 jdk 執行期間,動態建立 class 位元組碼並載入到 JVM。 動態代理的實現方式常用的有兩種: **使用 JDK 動態代理,和通過 CGLIB 動態代理。** ## 四、 JDK 動態代理 jdk 動態代理是基於 Java 的反射機制實現的。使用 jdk 中介面和類實現代理物件的動態建立。 **Jdk 的動態要求目標物件必須實現介面,這是 java 設計上的要求。** 從 jdk1.3 以來,java 語言通過 java.lang.reflect 包提供三個類支援代理模式 Proxy, Method 和 InvocationHandler ### 1. InvocationHandler介面 InvocationHandler 介面叫做呼叫處理器,負責完呼叫目標方法,並增強功能。 通過代理物件執行目標介面中的方法,會把方法的呼叫分派給呼叫處理器 (InvocationHandler)的實現類, 執行實現類中的 invoke()方法,我們需要把功能代理寫在 invoke ()方法中 **在 invoke 方法中可以擷取對目標方法的呼叫。在這裡進行功能增強** **invoke()方法:** ```java public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { } // proxy:代表生成的代理物件 // method:代表目標方法 // args:代表目標方法的引數 //第一個引數 proxy 是 jdk 在執行時賦值的,在方法中直接使用,第二個引數後面介紹, 第三個引數是方法執行的引數, 這三個引數都是 jdk 執行時賦值的,無需程式設計師給出。 ``` Java 的動態代理是 建立在反射機制之上的。 實現了 InvocationHandler 介面的類用於加強目標類的主業務邏輯。這個介面中有一個 方法 invoke(),具體加強的程式碼邏輯就是定義在該方法中的,**通過代理物件執行介面中的方法時,會自動呼叫 invoke()方法,** **總結:** InvocationHandler 介面:表示你的代理要幹什麼,怎麼用: - 建立類實現介面InvocationHandler - 重寫invoke()方法, 把原來靜態代理中代理類要完成的功能,寫在這 ### 2. Method 類 invoke()方法的第二個引數為 Method 類物件,該類有一個方法也叫 invoke(),可以呼叫目標方法。這兩個 invoke()方法,雖然同名,但無關。 這個類的invoke方法 ```java public Object invoke(Object obj , Object...args){ } // obj:表示目標物件 // args:表示目標方法引數,也就是上面invoke方法中的第三個引數 ``` 該方法的作用是: - 呼叫執行 obj 物件所屬類的方法,這個方法由其呼叫者 Method 物件確定。 在程式碼中,一般的寫法為 method.invoke(target, args); 其中,method 為上一層 invoke 方法的第二個引數。這樣,即可呼叫了目標類的目標方法。 ### 3. Proxy類 通過 JDK 的 java.lang.reflect.Proxy 類 實 現 動 態 代 理 ,會 使 用 其 靜 態 方 法 newProxyInstance(),依據目標物件、業務介面及呼叫處理器三者,自動生成一個動態代理對 象 Proxy類:核心的物件,建立代理物件。之前建立物件都是 new 類的構造方法() 現在我們是使用Proxy類的方法,代替new的使用。 方法: 靜態方法 newProxyInstance() 作用是: 建立代理物件, 等同於靜態代理中的TaoBao taoBao = new TaoBao(); ```java 返回值:就是代理物件 public static Object newProxyInstance(ClassLoader loader,Class[] interfaces,InvocationHandler h) // 1. ClassLoader loader 類載入器,負責向記憶體中載入物件的。 使用反射獲取物件的ClassLoader類a , a.getCalss().getClassLoader(), 目標物件的類載入器 // 2. Class[] interfaces: 介面, 目標物件實現的介面,也是反射獲取的。 // 3. InvocationHandler h : 我們自己寫的,代理類要完成的功能。 ``` ### 4. 實現動態代理的步驟 和靜態代理實現相同的功能 1. **建立介面,定義目標類要完成的功能** ```java package com.md.service; /** * @author MD * @create 2020-08-03 10:36 */ // 目標介面 public interface UsbSell { float sell(int count); } ``` 1. **建立目標類實現介面** ```java package com.md.factory; import com.md.service.UsbSell; /** * @author MD * @create 2020-08-03 10:37 */ // 目標類 public class UsbKingFactory implements UsbSell { // 目標方法 @Override public float sell(int count) { System.out.println("目標類:執行了目標方法"); return 50.0f; } } ``` 1. **建立InvocationHandler介面的實現類,在invoke方法中完成代理類的功能** ```java package com.md.handler; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; /** * @author MD * @create 2020-08-03 10:43 */ // 必須實現InvocationHandler介面,完成代理類需要的功能 // 1. 呼叫目標方法 // 2. 增強功能 public class MySellHandler implements InvocationHandler { // private Object target = null; // 動態代理,目標物件不是固定的,需要什麼就傳入什麼,之後通過 new MySellHandler(),就可以把目標物件傳進來 public MySellHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 1. 向廠家傳送訂單,告訴廠家,進行發貨,這是進貨價格 // float price = factory.sell(amount); // 2. 商家加價獲得利潤,增強功能 //price+=50; // 3. 在目標類的方法呼叫之後,剩下寫的其他功能,都是增加功能 // System.out.println("淘寶商家給你一個大的優惠卷"); // 1. 執行目標方法,和上面等價,呼叫目標方法,傳入目標物件和相應的引數 Object res = method.invoke(target,args); // 2. 增強功能 if (res != null){ Float price = (Float)res; price+=50; res = price; } // 3. 在目標類的方法呼叫之後,剩下寫的其他功能,都是增加功能 System.out.println("淘寶商家給你一個大的優惠卷"); return res; } } ``` 1. **使用Proxy類的靜態方法,建立代理物件。 並把返回值轉為介面型別** ```java package com.md; import com.md.factory.UsbKingFactory; import com.md.handler.MySellHandler; import com.md.service.UsbSell; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; /** * @author MD * @create 2020-08-03 10:55 */ public class Shopping { public static void main(String[] args) { // 建立代理物件,使用Proxy // 1. 建立目標物件 UsbSell factory = new UsbKingFactory(); // 2. 建立InvocationHandler物件 InvocationHandler handler = new MySellHandler(factory); // 3. 建立代理物件 UsbSell proxy = (UsbSell) Proxy.newProxyInstance(factory.getClass().getClassLoader(), factory.getClass().getInterfaces(), handler); // 4. 通過代理執行方法 float price = proxy.sell(1); System.out.println("通過動態代理呼叫方法:"+price); } } ``` ## 五、cgLib 代理 CGLIB(Code Generation Library)是一個開源專案。是一個強大的,高效能,高質量的 Code 生成類 庫,它可以在執行期擴充套件 Java 類與實現 Java 介面。它廣泛的被許多 AOP 的框架使用,例如 Spring AOP。 **使用 JDK 的 Proxy 實現代理,要求目標類與代理類實現相同的介面。若目標類不存在 介面,則無法使用該方式實現。** 但對於無介面的類,要為其建立動態代理,就要使用 CGLIB 來實現。 **CGLIB 代理的生成 原理是生成目標類的子類,而子類是增強過的,這個子類物件就是代理物件。所以,使用 CGLIB 生成動態代理,要求目標類必須能夠被繼承,即不能是 final 的類。** cglib 經常被應用在框架中,例如 Spring ,Hibernate 等。Cglib 的代理效率高於 Jdk。對 於 cglib 一般的開發中並不使用。做了一個瞭解就可以。 ## 六、練習 1. 定義介面 ```java package com.md.service; /** * @author MD * @create 2020-08-03 20:51 */ public interface Dog { void info(); void run(); } ``` 1. 介面的具體實現類 ```java package com.md.factory; import com.md.service.Dog; /** * @author MD * @create 2020-08-03 20:51 */ public class GunDog implements Dog { @Override public void info() { System.out.println("我是狗"); } @Override public void run() { System.out.println("狗在跑"); } } ``` 1. 增加方法 ```java package com.md.Util; /** * @author MD * @create 2020-08-03 20:53 */ public class DogUtil { public void method1(){ System.out.println("我是第一個增強方法"); } public void method2(){ System.out.println("我是第二個增強方法"); } } ``` 1. 實現 InnovationHandler 介面 動態代理的核心環節就是要實現 InvocaitonHandler 介面。每個動態代理類都必須要實現 InvocationHandler 介面,並且每個代理類的例項都關聯到了一個 InvocationHandler 介面實現類的例項物件,當我們通過代理物件呼叫一個方法的時候,這個方法的呼叫就會被轉發為由 InvocationHandler 這個介面的 invoke() 方法來進行呼叫,具體的實現如下: ```java package com.md.handler; import com.md.Util.DogUtil; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; /** * @author MD * @create 2020-08-03 20:55 */ public class MyHandler implements InvocationHandler { private Object target; // 需要被代理的物件 public void setTarget(Object target) { this.target = target; } // 當我們呼叫被代理物件的方法時,invvoke方法會取而代之 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { DogUtil dogUtil = new DogUtil(); // 增強方法 dogUtil.method1(); // 用反射呼叫Dog class中的方法 Object res = method.invoke(target, args); dogUtil.method2(); return res; } } ``` 1. **使用Proxy類的靜態方法,建立代理物件。 並把返回值轉為介面型別** ```java package com.md; import com.md.factory.GunDog; import com.md.handler.MyHandler; import com.md.service.Dog; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; /** * @author MD * @create 2020-08-03 21:14 */ public class DogTest { public static void main(String[] args) { // 例項化被代理物件 Dog dog = new GunDog(); // 例項化MyHandler物件, MyHandler myHandler = new MyHandler(); myHandler.setTarget(dog); // 用被代理物件產生一個代理物件 Dog proxy = (Dog) Proxy.newProxyInstance(dog.getClass().getClassLoader(), dog.getClass().getInterfaces(), myHandler); // 代理物件呼叫被代理物件的方法 proxy.info(); System.out.println("-------------------"); proxy.run(); } } // 執行結果 /* 我是第一個增強方法 我是狗 我是第二個增強方法 ------------------- 我是第一個增強方法 狗在跑 我是第二個增強方法 */ ``` 還是需要注意newProxyInstance() ,要把返回值轉為介面型別,以及注意引數 - 引數一:類載入器物件即用哪個類載入器來載入這個代理類到 JVM 的方法區 - 引數二:介面表明該代理類需要實現的介面 - 引數三:是呼叫處理器類例項即 InvocationHandler 的實現的例項物件 這個函式是 JDK 為了程式設計師方便建立代理物件而封裝的一個函式,因此你呼叫`newProxyInstance()`時直接建立了代理