動態代理兩種方式
阿新 • • 發佈:2021-12-02
1.動態代理兩種方式簡述
JDK動態代理:利用反射機制生成一個實現代理介面的匿名類,在呼叫具體方法前呼叫InvokeHandler來處理。
CGLib動態代理:利用ASM(開源的Java位元組碼編輯庫,操作位元組碼)開源包,將代理物件類的class檔案載入進來,通過修改其位元組碼生成子類來處理。
區別:JDK代理只能對實現介面的類生成代理;CGlib是針對類實現代理,對指定的類生成一個子類,並覆蓋其中的方法,這種通過繼承類的實現方式,不能代理final修飾的類。
2.動態代理的特點
1.位元組碼隨用隨建立,隨用隨載入。 2.它與靜態代理的區別也在於此。因為靜態代理是位元組碼一上來就建立好,並完成載入。3.動態代理常用的有兩種方式
3.1 基於介面的動態代理
提供者:JDK 官方的 Proxy 類。 要求:被代理類最少實現一個介面。3.2 基於子類的動態代理
提供者:第三方的 CGLib,如果報 asmxxxx 異常,需要匯入 asm.jar。 要求:被代理類不能用 final 修飾的類(最終類)。4.使用JDK官方的Porxy類建立物件
實體類:
package com.jh.spring13jdk動態代理; import lombok.Data; @Data public class Game implements Open{//遊戲的網速 private int ms = 460; @Override public int openApp() { System.out.println("開啟遊戲後的網速是:" + this.getMs()); return this.getMs(); } }
介面:
package com.jh.spring13jdk動態代理; public interface Open { int openApp(); }
測試類:
package com.jh.spring13jdk動態代理; import org.junit.Test;import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class Spring13Test { //JDK動態代理 省略了代理物件!!! 直接有jdk中的代理類來實現,但是目標類必須有介面 @Test public void test01() { final Game game = new Game(); /** * 代理: * 間接。 * 獲取代理物件: * 要求: * 被代理類最少實現一個介面 * 建立的方式 * Proxy.newProxyInstance(三個引數) * 引數含義: * ClassLoader:和被代理物件使用相同的類載入器。 * Interfaces:和被代理物件具有相同的行為。實現相同的介面。 * InvocationHandler:如何代理。 * 策略模式:使用場景是: * 資料有了,目的明確。 * 如何達成目標,就是策略。 * */ //使用JDK動態類物件,當作迅遊加速器的類,代替了靜態的代理類 //Proxy:代理的意思。 newProxyInstancece建立代理物件 Open jdkProxy = (Open) Proxy.newProxyInstance( game.getClass().getClassLoader(), //類的載入器 game.getClass().getInterfaces(), //類的所有介面 new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Integer ms = (Integer) method.invoke(game, args); if (ms != null) { ms = ms - 400; } return ms; } } ); int i = jdkProxy.openApp(); System.out.println("i=" + i); } }
5.使用CGLib的Enhancer類建立代理物件
對於沒有介面的類,如何實現動態代理呢,這就需要CGLib了,CGLib採用了非常底層的位元組碼技術,其原理是通過位元組碼技術為一個類建立子類,並在子類中採用方法攔截的技術攔截所有父類方法的呼叫,順勢織入橫切邏輯。但因為採用的是繼承。所以不能對final修飾的類進行代理。
實體類:
package com.jh.spring14cjlib動態代理; import lombok.Data; /** * 目標類 * 父母 */ @Data public class Parents { //成績 private int score = 599; //高考 public int gaoKao(){ System.out.println("父母參加高考,分數是:"+this.getScore()); return this.getScore(); } }
工廠類:
package com.jh.spring14cjlib動態代理; import org.springframework.cglib.proxy.Enhancer; import org.springframework.cglib.proxy.MethodInterceptor; import org.springframework.cglib.proxy.MethodProxy; import java.lang.reflect.Method; //MethodInterceptor:方法上的攔截 public class ParentsFactory implements MethodInterceptor { private Parents parents; public ParentsFactory() { parents = new Parents(); } /** * 基於子類的動態代理 * 要求: * 被代理物件不能是最終類 * 用到的類: * Enhancer * 用到的方法: * create(Class, Callback) * 方法的引數: * Class:被代理物件的位元組碼 * Callback:如何代理 */ //增強器 ,把parents創造一個子類 public Parents createParentsSon() { //使用位元組碼增強器,去增強我們的父類 Enhancer enhancer = new Enhancer(); //位元組碼增強器,可以讀懂class檔案 //enhancer 指定一個物件 enhancer.setSuperclass(Parents.class);//反射 //使用工廠,換行(建立子類) enhancer.setCallback(this); //建立子類 Parents son = (Parents) enhancer.create(); //多型 return son; } /** * 執行被代理物件的任何方法,都會經過該方法。在此方法內部就可以對被代理物件的任何 方法進行增強。 * * 引數: * 前三個和基於介面的動態代理是一樣的。 * MethodProxy:當前執行方法的代理物件。 * 返回值: * 當前執行方法的返回值 */ //方法的攔截 @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { Integer score = (Integer) method.invoke(parents, objects); if (score != null) { score = score + 30; } return score; } }
測試類:
package com.jh.spring14cjlib動態代理; import org.junit.Test; public class Spring14Test { @Test public void test01(){ ParentsFactory parentsFactory = new ParentsFactory(); Parents parentsSon = parentsFactory.createParentsSon(); int score = parentsSon.getScore(); System.out.println(score); } }
6.總結:
CGLib建立的動態代理物件比JDK建立的動態代理物件的效能更高,但是CGLib建立物件時所花費的時間卻比JDK多的的。
7.問題:
為什麼,要在不改變原始碼的基礎上,去寫一個代理類增強一些功能呢?
因為專案大了,就有主要的功能和次要的功能,要想主要功能和次要功能一起執行,必須用AOP
我再解釋下面向物件和麵向切面,面向物件OOP,面向的是主要功能的物件,而AOP是面向物件OOP的一個補充,面向次要功能的物件
目的是為了降低耦合度,提高程式碼的複用性。(自己總結的,僅供參考,你懂的,嘻嘻)。