動態代理(理解): 基於反射機制。
如今須要知道如下的就行:java
什麼是動態代理 ?程序員
使用jdk的反射機制,建立對象的能力, 建立的是代理類的對象。 而不用你建立類文件。不用寫java文件。
動態:在程序執行時,調用jdk提供的方法才能建立代理類的對象。spring
jdk動態代理,必須有接口,目標類必須實現接口, 沒有接口時,須要使用cglib動態代理mybatis
能夠在不改變原來目標方法功能的前提下, 能夠在代理中加強本身的功能代碼。
程序開發中的意思。
好比:你所在的項目中,有一個功能是其餘人(公司的其它部門,其它小組的人)寫好的,你可使用。
GoNong.class , GoNong gn = new GoNong(), gn.print();框架
你發現這個功能,如今還缺點, 不能徹底知足我項目的須要。 我須要在gn.print()執行後,須要本身在增長代碼。
用代理實現 gn.print()調用時, 增長本身代碼, 而不用去改原來的 GoNong文件ide
後面會有,mybatis ,spring函數
代購, 中介,換ip,商家等等性能
好比有一家美國的大學, 能夠對全世界招生。 留學中介(代理)this
留學中介(代理): 幫助這家美國的學校招生, 中介是學校的代理, 中介是代替學校完成招生功能。
代理特色:
1. 中介和代理他們要作的事情是一致的: 招生。
2. 中介是學校代理, 學校是目標。
3. 家長---中介(學校介紹,辦入學手續)----美國學校。
4. 中介是代理,不能白乾活,須要收取費用。
5. 代理不讓你訪問到目標。設計
或者是買東西都是商家賣, 商家是某個商品的代理, 你我的買東西, 確定不會讓你接觸到廠家的。
在開發中也會有這樣的狀況, 你有a類, 原本是調用c類的方法, 完成某個功能。 可是c不讓a調用。
a -----不能調用 c的方法。
在a 和 c 直接 建立一個 b 代理, c讓b訪問。
a --訪問b---訪問c
靜態代理和動態代理
靜態代理是指,代理類在程序運行前就已經定義好.java 源文件,其與目標類的關係在 程序運行前就已經確立。在程序運行前代理類已經編譯爲.class 文件。
代理類是本身手工實現的,本身建立一個java類,表示代理類。
同時你所要代理的目標類是肯定的。
特色: 實現簡單 、容易理解
缺點:
當你的項目中,目標類和代理類不少時候,有如下的缺點:
用戶是客戶端類
實現步驟:
package com.md.service; /** * @author MD * @create 2020-08-03 9:06 */ // 表示功能,廠家和商家都要完成的功能 public interface UsbSell { // 定義方法,返回值爲u盤的價格 float sell(int amount); }
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; } }
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; } }
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; } }
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); } }
代碼複雜,難於管理
代理類和目標類實現了相同的接口,每一個代理都須要實現目標類的方法,這樣就出現了大量的代 碼重複。若是接口增長一個方法,除了全部目標類須要實現這個方法外,全部代理類也須要實現 此方法。增長了代碼維護的複雜度。
代理類依賴目標類,代理類過多
代理類只服務於一種類型的目標類,若是要服務多個類型。勢必要爲每一種目標類都進行代理, 靜態代理在程序規模稍大時就沒法勝任了,代理類數量過多。
動態代理是指代理類對象在程序運行時由 JVM 根據反射機制動態生成的。動態代理不須要定義代理類的.java 源文件。
動態代理其實就是 jdk 運行期間,動態建立 class 字節碼並加載到 JVM。
動態代理的實現方式經常使用的有兩種:
使用 JDK 動態代理,和經過 CGLIB 動態代理。
jdk 動態代理是基於 Java 的反射機制實現的。使用 jdk 中接口和類實現代理對象的動態建立。
Jdk 的動態要求目標對象必須實現接口,這是 java 設計上的要求。
從 jdk1.3 以來,java 語言經過 java.lang.reflect 包提供三個類支持代理模式 Proxy, Method 和 InvocationHandler
InvocationHandler 接口叫作調用處理器,負責完調用目標方法,並加強功能。
經過代理對象執行目標接口中的方法,會把方法的調用分派給調用處理器 (InvocationHandler)的實現類,
執行實現類中的 invoke()方法,咱們須要把功能代理寫在 invoke ()方法中
在 invoke 方法中能夠截取對目標方法的調用。在這裏進行功能加強
invoke()方法:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { } // proxy:表明生成的代理對象 // method:表明目標方法 // args:表明目標方法的參數 //第一個參數 proxy 是 jdk 在運行時賦值的,在方法中直接使用,第二個參數後面介紹, 第三個參數是方法執行的參數, 這三個參數都是 jdk 運行時賦值的,無需程序員給出。
Java 的動態代理是 創建在反射機制之上的。 實現了 InvocationHandler 接口的類用於增強目標類的主業務邏輯。這個接口中有一個 方法 invoke(),具體增強的代碼邏輯就是定義在該方法中的,經過代理對象執行接口中的方法時,會自動調用 invoke()方法,
總結:
InvocationHandler 接口:表示你的代理要幹什麼,怎麼用:
invoke()方法的第二個參數爲 Method 類對象,該類有一個方法也叫 invoke(),能夠調用目標方法。這兩個 invoke()方法,雖然同名,但無關。
這個類的invoke方法
public Object invoke(Object obj , Object...args){ } // obj:表示目標對象 // args:表示目標方法參數,也就是上面invoke方法中的第三個參數
該方法的做用是:
在代碼中,通常的寫法爲 method.invoke(target, args); 其中,method 爲上一層 invoke 方法的第二個參數。這樣,便可調用了目標類的目標方法。
經過 JDK 的 java.lang.reflect.Proxy 類 實 現 動 態 代 理 ,會 使 用 其 靜 態 方 法 newProxyInstance(),依據目標對象、業務接口及調用處理器三者,自動生成一個動態代理對 象
Proxy類:核心的對象,建立代理對象。以前建立對象都是 new 類的構造方法()
如今咱們是使用Proxy類的方法,代替new的使用。
方法: 靜態方法 newProxyInstance()
做用是: 建立代理對象, 等同於靜態代理中的TaoBao taoBao = new TaoBao();
返回值:就是代理對象 public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) // 1. ClassLoader loader 類加載器,負責向內存中加載對象的。 使用反射獲取對象的ClassLoader類a , a.getCalss().getClassLoader(), 目標對象的類加載器 // 2. Class<?>[] interfaces: 接口, 目標對象實現的接口,也是反射獲取的。 // 3. InvocationHandler h : 咱們本身寫的,代理類要完成的功能。
和靜態代理實現相同的功能
package com.md.service; /** * @author MD * @create 2020-08-03 10:36 */ // 目標接口 public interface UsbSell { float sell(int count); }
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; } }
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; } }
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(Code Generation Library)是一個開源項目。是一個強大的,高性能,高質量的 Code 生成類 庫,它能夠在運行期擴展 Java 類與實現 Java 接口。它普遍的被許多 AOP 的框架使用,例如 Spring AOP。
使用 JDK 的 Proxy 實現代理,要求目標類與代理類實現相同的接口。若目標類不存在 接口,則沒法使用該方式實現。
但對於無接口的類,要爲其建立動態代理,就要使用 CGLIB 來實現。
CGLIB 代理的生成 原理是生成目標類的子類,而子類是加強過的,這個子類對象就是代理對象。因此,使用 CGLIB 生成動態代理,要求目標類必須可以被繼承,即不能是 final 的類。
cglib 常常被應用在框架中,例如 Spring ,Hibernate 等。Cglib 的代理效率高於 Jdk。對 於 cglib 通常的開發中並不使用。作了一個瞭解就能夠。
package com.md.service; /** * @author MD * @create 2020-08-03 20:51 */ public interface Dog { void info(); void run(); }
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("狗在跑"); } }
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("我是第二個加強方法"); } }
實現 InnovationHandler 接口
動態代理的核心環節就是要實現 InvocaitonHandler 接口。每一個動態代理類都必需要實現 InvocationHandler 接口,而且每一個代理類的實例都關聯到了一個 InvocationHandler 接口實現類的實例對象,當咱們經過代理對象調用一個方法的時候,這個方法的調用就會被轉發爲由 InvocationHandler 這個接口的 invoke() 方法來進行調用,具體的實現以下:
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; } }
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() ,要把返回值轉爲接口類型,以及注意參數
這個函數是 JDK 爲了程序員方便建立代理對象而封裝的一個函數,所以你調用newProxyInstance()
時直接建立了代理對象