代理模式(Proxy Pattern):爲其餘對象提供一種代理以控制
對這個對象的訪問。簡而言之,既能使被代理對象無入侵
,又能附加代理本身的操做,使方法加強功能
。java
水果店代理銷售海南芝麻蕉,此外還銷售蘋果、橘子等其餘水果。
git
代理的主要實現技術與方法以下圖所示,本篇主要講靜態代理與動態代理的主要實現方式,原理部分的深刻,以及ASM字節碼技術,將放到後續篇幅講解。
程序員
下面分多個版本,經過逐步演進的方式,講解代理模式,其中版本1到版本6爲靜態代理的逐步演進過程,版本7-9爲JDK動態代理內容,AspjectJ靜態代理與Cglib動態代理單獨演示。github
假設水果店有待銷蘋果,並參與秒殺活動,定義Apple
類,待銷水果接口Sellable
,接口秒殺方法secKill()
,代碼以下:spring
package com.wzj.proxy.v1; /** * @Author: wzj * @Date: 2020/8/3 10:29 * @Desc: 蘋果 */ public class Apple implements Sellalbe { @Override public void secKill() { System.out.println("蘋果正在秒殺中..."); try { Thread.sleep(new Random().nextInt(3000)); } catch (InterruptedException e) { e.printStackTrace(); } } }
package com.wzj.proxy.v1; /** * @Author: wzj * @Date: 2020/8/3 10:56 * @Desc: 待銷水果 */ public interface Sellalbe { /** * 秒殺 */ public void secKill(); }
現有個問題1,如需記錄秒殺時間,該如何修改代碼呢?express
針對問題1,直接修改秒殺方法app
package com.wzj.proxy.v2; import java.util.Random; /** * @Author: wzj * @Date: 2020/8/3 10:29 * @Desc: 待銷蘋果 * 問題1:記錄蘋果秒殺的具體時間 * 最簡單的作法就是修改代碼 */ public class Apple implements Sellalbe { @Override public void secKill() { Long start = System.currentTimeMillis(); System.out.println("蘋果正在秒殺中..."); try { Thread.sleep(new Random().nextInt(3000)); } catch (InterruptedException e) { e.printStackTrace(); } Long end = System.currentTimeMillis(); System.out.println("記錄秒殺時間爲:" + (end - start)); } }
測試代碼以下框架
package com.wzj.proxy.v2; /** * @Author: wzj * @Date: 2020/8/3 15:13 * @Desc: */ public class Test2 { public static void main(String[] args) { new Apple().secKill(); } }
結果:dom
蘋果正在秒殺中... 記錄秒殺時間爲:2944
如今趕上問題2,若是沒法改變方法源碼呢,對源代碼無入侵,該如何作呢?maven
針對問題2,很容易想到,用繼承
解決。新增一個類Apple2
,從Apple
繼承。
package com.wzj.proxy.v3; /** * @Author: wzj * @Date: 2020/8/3 15:25 * @Desc: 繼承實現 */ public class Apple2 extends Apple { @Override public void secKill() { long start = System.currentTimeMillis(); super.secKill(); long end = System.currentTimeMillis(); System.out.println("記錄秒殺時間爲:" + (end - start)); } }
測試類:
package com.wzj.proxy.v3; /** * @Author: wzj * @Date: 2020/8/3 15:13 * @Desc: */ public class Test3 { public static void main(String[] args) { new Apple2().secKill(); } }
結果:
蘋果正在秒殺中... 記錄秒殺時間爲:1660
需求老是變化不斷,問題3又來了,若是須要記錄秒殺開始前與開始後的日誌,該如何修改呢?可能想到設計一個日誌類Apple3
,從Apple
類繼承;
若是需求變化了,先要記錄日誌,再記錄秒殺時間,是否是要繼續設計一個先日誌後時間的類Apple4
,從Apple3
繼承?
若是需求又變了,先記錄秒殺時間,再記錄秒殺先後日誌,是否是又得從新設計Apple5
,從Apple4
繼承?
若是需求再次變化,須要先記錄權限,而後記錄秒殺時間,最後秒殺日誌,發現須要一直不停的設計新的類,並繼承於各類派生出來的類,容易產生類爆炸
,且難以複用,不靈活,該如何解決呢?
出現問題3的根源是,繼承破壞封裝
,集合框架的創始人Joshua Bloch,在其著做《effective java》一書時,指出複合優於繼承
這一原則,很好的解決了繼承帶來的脆弱性。針對問題3,設計一個類記錄秒殺時間的代理類AppleTimeProxy
:
package com.wzj.proxy.v4; /** * @Author: wzj * @Date: 2020/8/3 15:46 * @Desc: 記錄時間的代理 * 組合優於繼承 */ public class AppleTimeProxy implements Sellalbe{ Apple apple; public AppleTimeProxy(Apple apple) { this.apple = apple; } @Override public void secKill() { long start = System.currentTimeMillis(); apple.secKill(); long end = System.currentTimeMillis(); System.out.println("記錄秒殺時間爲:" + (end - start)); } }
針對問題3,若是要代理各類類型,好比代理記錄秒殺時間
,代理記錄日誌
,每一個類只須要單一代理各自的功能,只須要增長代碼以下:
package com.wzj.proxy.v5; /** * @Author: wzj * @Date: 2020/8/3 21:39 * @Desc: 記錄日誌的代理類 */ public class AppleLogProxy implements Sellalbe { Apple apple; public AppleLogProxy(Apple apple) { this.apple = apple; } @Override public void secKill() { System.out.println("秒殺開始..."); apple.secKill(); System.out.println("秒殺結束..."); } }
package com.wzj.proxy.v5; /** * @Author: wzj * @Date: 2020/8/3 15:46 * @Desc: 記錄時間的代理 * 組合優於繼承 */ public class AppleTimeProxy implements Sellalbe { Apple apple; public AppleTimeProxy(Apple apple) { this.apple = apple; } @Override public void secKill() { long start = System.currentTimeMillis(); apple.secKill(); long end = System.currentTimeMillis(); System.out.println("記錄秒殺時間爲:" + (end - start)); } }
進一步優化上述代碼,將組合進來的類型Apple
,改形成接口sellable
。
package com.wzj.proxy.v6; /** * @Author: wzj * @Date: 2020/8/3 21:39 * @Desc: 記錄日誌的代理類 */ public class AppleLogProxy implements Sellalbe { Sellalbe sellalbe; public AppleLogProxy(Sellalbe sellalbe) { this.sellalbe = sellalbe; } @Override public void secKill() { System.out.println("秒殺開始..."); sellalbe.secKill(); System.out.println("秒殺結束..."); } }
package com.wzj.proxy.v6; /** * @Author: wzj * @Date: 2020/8/3 15:46 * @Desc: 記錄時間的代理 * 組合優於繼承 */ public class AppleTimeProxy implements Sellalbe { Sellalbe sellalbe; public AppleTimeProxy(Sellalbe sellalbe) { this.sellalbe = sellalbe; } @Override public void secKill() { long start = System.currentTimeMillis(); sellalbe.secKill(); long end = System.currentTimeMillis(); System.out.println("記錄秒殺時間爲:" + (end - start)); } }
這樣作能夠達到,在不增長類的狀況下,能夠實現自由嵌套
,先記錄時間,後打印日誌
,或者先打印日誌,後記錄時間
,能夠在客戶端發起,不用修改源代碼。測試代碼以下:
package com.wzj.proxy.v6; /** * @Author: wzj * @Date: 2020/8/3 21:44 * @Desc: 如何實現代理的各類組合?繼承?Decorator? * 代理的對象改爲Sellable類型-愈來愈像decorator了,爲了實現嵌套 */ public class Test6 { public static void main(String[] args) { //先記錄時間,後打印日誌 new AppleLogProxy(new AppleTimeProxy(new Apple())).secKill(); System.out.println("==========================="); //先打印日誌,後記錄時間 new AppleTimeProxy(new AppleLogProxy(new Apple())).secKill(); } }
結果以下:
秒殺開始... 蘋果正在秒殺中... 記錄秒殺時間爲:2047 秒殺結束... =========================== 秒殺開始... 蘋果正在秒殺中... 秒殺結束... 記錄秒殺時間爲:2099
針對問題3的需求,若是要增長記錄權限的功能,或者是權限與記錄時間的功能組合,或者權限與記錄日誌的功能組合,或者權限、時間、日誌三個功能按照任意順序的組合,都不須要像v3版本那樣,每一個需求的變動,都須要設計新的類,各種之間冗餘度高,且臃腫。直接設計一個單獨權限的代理類,在客戶端作各類嵌套調用
便可。
第一種方式爲配置實現,在maven中加入Aspject相關依賴
<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.4</version> </dependency>
目標對象
package com.wzj.spring.v1; import com.wzj.proxy.v9.Sellalbe; import java.util.Random; /** * @Author: wzj * @Date: 2020/8/3 10:29 * @Desc: 待銷蘋果 */ public class Apple { public void secKill() { System.out.println("蘋果正在秒殺中..."); try { Thread.sleep(new Random().nextInt(3000)); } catch (InterruptedException e) { e.printStackTrace(); } } }
切面類
package com.wzj.spring.v1; import com.wzj.proxy.v6.Sellalbe; import java.lang.reflect.Method; /** * @Author: wzj * @Date: 2020/8/3 15:46 * @Desc: 記錄時間的代理 */ public class AppleTimeProxy { private void after() { System.out.println("方法執行結束時間:" + System.currentTimeMillis()); } private void before() { System.out.println("方法執行開始時間:" + System.currentTimeMillis()); } }
app.xml
配置文件
<bean id="tank" class="com.mashibing.dp.spring.v1.Tank"/> <bean id="timeProxy" class="com.mashibing.dp.spring.v1.TimeProxy"/> <aop:config> <aop:aspect id="time" ref="timeProxy"> <aop:pointcut id="onmove" expression="execution(void com.mashibing.dp.spring.v1.Tank.move())"/> <aop:before method="before" pointcut-ref="onmove"/> <aop:after method="after" pointcut-ref="onmove"/> </aop:aspect> </aop:config>
測試類
package com.wzj.spring.v1; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; /** * @Author: wzj * @Date: 2020/8/5 15:09 * @Desc: */ public class TestAOP { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("app.xml"); Apple apple = (Apple)context.getBean("apple"); apple.secKill(); } }
第二種方式爲註解實現,切面類以下
package com.wzj.spring.v2; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; /** * @Author: wzj * @Date: 2020/8/3 15:46 * @Desc: 記錄時間的代理 */ @Aspect public class AppleTimeProxy { @Before("execution (void com.wzj.spring.v2.Apple.secKill())") private void after() { System.out.println("方法執行結束時間:" + System.currentTimeMillis()); } @After("execution (void com.wzj.spring.v2.Apple.secKill())") private void before() { System.out.println("方法執行開始時間:" + System.currentTimeMillis()); } }
配置類app-auto.xml
<aop:aspectj-autoproxy/> <bean id="apple" class="com.wzj.spring.v2.Apple"/> <bean id="timeProxy" class="com.wzj.spring.v2.AppleTimeProxy"/>
測試類
package com.wzj.spring.v2; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; /** * @Author: wzj * @Date: 2020/8/5 15:09 * @Desc: */ public class TestAOP { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("app-auto.xml"); Apple apple = (Apple)context.getBean("apple"); apple.secKill(); } }
結果
方法執行開始時間:1596979946478 蘋果正在秒殺中... 方法執行結束時間:1596979948708
以上版本都是基於靜態代理
的實現,代理類由程序員編寫源碼,再編譯成字節碼文件,在編譯期間
生成,靜態代理類圖
缺點
一種類型
的對象,好比上面的水果店例子,只能代理Sellable
類型的對象,若是該水果店除了賣水果,門口還有小孩玩的投幣玩具車,新增一個Player
接口,那麼須要新增代理類;若是一個系統有100多個類須要經過代理來加強功能,程序規模龐大時沒法勝任;代理對象在運行期間
動態生成,能夠代理任何對象
的任何方法
。
優勢
JDK
動態代理的InvocationHandler
的invoke()
方法)。實現步驟以下:
InvocationHandlet
接口,並實現invoke()
方法建立本身的調用處理器;Proxy.newProxyInstance
方法建立代理對象,該方法有三個參數,分別爲被代理類的類加載器、被代理類接口、以及InvocationHandlet
的實現類;package com.wzj.proxy.v7; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; /** * @Author: wzj * @Date: 2020/8/4 8:45 * @Desc: 經過實現InvocationHandlet接口建立本身的調用處理器 */ public class LogHandler implements InvocationHandler { Apple apple; public LogHandler(Apple apple) { this.apple = apple; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("方法執行日誌開始,方法名爲:" + method.getName()); Object o = method.invoke(apple, args); System.out.println("方法執行日誌結束,方法名爲:" + method.getName()); return o; } }
package com.wzj.proxy.v7; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; /** * @Author: wzj * @Date: 2020/8/4 8:55 * @Desc: JDK實現動態代理 */ public class ProxyGenerator { public Object createProxy(Apple apple, InvocationHandler handler) { Object o = Proxy.newProxyInstance(apple.getClass().getClassLoader(), apple.getClass().getInterfaces(), handler); return o; } }
測試類
package com.wzj.proxy.v7; import com.wzj.proxy.v6.AppleLogProxy; import com.wzj.proxy.v6.AppleTimeProxy; import java.lang.reflect.InvocationHandler; /** * @Author: wzj * @Date: 2020/8/3 21:44 * @Desc: 若是想讓LogProxy能夠重用,不只能夠代理Apple,還能夠代理任何其餘能夠代理的類型 Object * (畢竟日誌記錄,時間計算是不少方法都須要的東西),這時該怎麼作呢? * 分離代理行爲與被代理對象 * 使用jdk的動態代理 */ public class Test7 { public static void main(String[] args) { Apple apple = new Apple(); InvocationHandler handler = new LogHandler(apple); Sellalbe sellalbe = (Sellalbe) new ProxyGenerator().createProxy(apple, handler); sellalbe.secKill(); } }
結果:
方法執行日誌開始,方法名爲:secKill 蘋果正在秒殺中... 方法執行日誌結束,方法名爲:secKill
針對版本7,若是如今有個橘子類Orange
,實現了打折接口Discount
,也須要在打折接口先後打印日誌,如何使用動態代理呢,畢竟打印日誌是通用功能,
該如何修改代碼呢?
版本8,經過泛型實現,代碼以下
package com.wzj.proxy.v8; /** * @Author: wzj * @Date: 2020/8/4 17:45 * @Desc: 待銷橘子 */ public class Orange implements Discount { /** * 打折優惠 */ @Override public int calculateBySourcePrice(int price) { int i= 9; System.out.println("橘子打折優惠, 一概9元"); return i; } }
package com.wzj.proxy.v8; /** * @Author: wzj * @Date: 2020/8/5 20:56 * @Desc: 折扣優惠接口 */ public interface Discount { public int calculateBySourcePrice(int price); }
package com.wzj.proxy.v8; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; /** * @Author: wzj * @Date: 2020/8/4 8:45 * @Desc: 使用泛型,代理任何對象的任何行爲 */ public class LogHandler<T> implements InvocationHandler { T t; public LogHandler(T t) { this.t = t; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { before(method); Object o = method.invoke(t, args); after(method); return o; } private void after(Method method) { System.out.println("方法執行日誌結束,方法名爲:" + method.getName()); } private void before(Method method) { System.out.println("方法執行日誌開始,方法名爲:" + method.getName()); } }
package com.wzj.proxy.v8; import com.wzj.proxy.v8.LogHandler; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; /** * @Author: wzj * @Date: 2020/8/4 8:55 * @Desc: 使用泛型,代理任何類的任何接口 */ public class ProxyGenerator<T> { public Object createProxy(T t, InvocationHandler handler) { Object o = Proxy.newProxyInstance(t.getClass().getClassLoader(), t.getClass().getInterfaces(), handler); return o; } }
測試類:
package com.wzj.proxy.v8; import java.lang.reflect.InvocationHandler; /** * @Author: wzj * @Date: 2020/8/3 21:44 * @Desc: 若是想讓LogProxy能夠重用,不只能夠代理Tank,還能夠代理任何其餘能夠代理的類型 Object * (畢竟日誌記錄,時間計算是不少方法都須要的東西),這時該怎麼作呢? * 分離代理行爲與被代理對象 * 使用jdk的動態代理 * * 增長泛型,既能夠代理蘋果秒殺的接口,也能夠代理橘子打折接口,實現任何對象任何接口的代理 */ public class Test8 { public static void main(String[] args) { Apple apple = new Apple(); InvocationHandler appHandler = new LogHandler(apple); Sellalbe sellalbe = (Sellalbe) new ProxyGenerator().createProxy(apple, appHandler); sellalbe.secKill(); System.out.println("================================="); Orange orange = new Orange(); InvocationHandler orgHandler = new LogHandler(orange); Discount discount = (Discount) new ProxyGenerator().createProxy(orange, orgHandler); discount.calculateBySourcePrice(10); } }
結果:
方法執行日誌開始,方法名爲:secKill 蘋果正在秒殺中... 方法執行日誌結束,方法名爲:secKill ================================= 方法執行日誌開始,方法名爲:calculateBySourcePrice 橘子打折優惠, 一概9元 方法執行日誌結束,方法名爲:calculateBySourcePrice
有人會有疑問,invoke()
方法並無顯示調用,爲什麼會執行呢?下面的版本9將代理類生成出來就會揭曉答案。
版本9
package com.wzj.proxy.v9; /** * @Author: wzj * @Date: 2020/8/4 21:23 * @Desc: 打印生成的代理 */ public class Test9 { public static void main(String[] args) { Apple apple = new Apple(); //在項目目錄下生成代理類 System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true"); LogHandler handler = new LogHandler(apple); Sellalbe sellalbe = (Sellalbe) new ProxyGenerator().createProxy(apple, handler); sellalbe.secKill(); } }
代理類如圖:
發現生成了一個$Proxy0
的類,
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package com.sun.proxy; import com.wzj.proxy.v9.Sellalbe; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.lang.reflect.UndeclaredThrowableException; public final class $Proxy0 extends Proxy implements Sellalbe { private static Method m1; private static Method m3; private static Method m2; private static Method m0; public $Proxy0(InvocationHandler var1) throws { super(var1); } public final boolean equals(Object var1) throws { try { return (Boolean)super.h.invoke(this, m1, new Object[]{var1}); } catch (RuntimeException | Error var3) { throw var3; } catch (Throwable var4) { throw new UndeclaredThrowableException(var4); } } public final void secKill() throws { try { super.h.invoke(this, m3, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final String toString() throws { try { return (String)super.h.invoke(this, m2, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final int hashCode() throws { try { return (Integer)super.h.invoke(this, m0, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } static { try { m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object")); m3 = Class.forName("com.wzj.proxy.v9.Sellalbe").getMethod("secKill"); m2 = Class.forName("java.lang.Object").getMethod("toString"); m0 = Class.forName("java.lang.Object").getMethod("hashCode"); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); } } }
進入該類,在調用secKill()
方法裏面有這麼一段代碼super.h.invoke(this, m3, (Object[])null)
,顯然是在代理類調用secKill()
方法時,裏面調用了自定義的handler
的invoke()
方法。下圖來經過調試驗證一下
顯然h
是自定義的LogHandler
。
圖解以下:
代碼實現:
package com.wzj.cglib; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method; /** * @Author: wzj * @Date: 2020/8/7 11:09 * @Desc: */ public class LogMethodInterceptor implements MethodInterceptor { @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { //代理類的父類信息 System.out.println("代理類的父類信息:" + o.getClass().getSuperclass().getName()); //前置加強 before(method); Object result = null; //調用被代理對象的方法 result = methodProxy.invokeSuper(o, objects); //後置加強 after(method); return result; } private void after(Method method) { System.out.println("方法執行日誌結束,方法名爲:" + method.getName()); } private void before(Method method) { System.out.println("方法執行日誌開始,方法名爲:" + method.getName()); } }
package com.wzj.cglib; import net.sf.cglib.proxy.Enhancer; /** * @Author: wzj * @Date: 2020/8/7 15:13 * @Desc: */ public class CglibProxy<T> { public T createProxy(T t) { //加強器 Enhancer enhancer = new Enhancer(); //設置父類 enhancer.setSuperclass(t.getClass()); //設置回調的攔截器 enhancer.setCallback(new LogMethodInterceptor()); T proxy = (T) enhancer.create(); return proxy; } }
package com.wzj.cglib; import java.util.Random; /** * @Author: wzj * @Date: 2020/8/3 10:29 * @Desc: 待銷蘋果 */ public class Apple { public void secKill() { System.out.println("蘋果正在秒殺中..."); try { Thread.sleep(new Random().nextInt(3000)); } catch (InterruptedException e) { e.printStackTrace(); } } }
測試代碼:
package com.wzj.cglib; import net.sf.cglib.core.DebuggingClassWriter; import net.sf.cglib.proxy.Enhancer; /** * @Author: wzj * @Date: 2020/8/7 14:16 * @Desc: */ public class CglibTest { public static void main(String[] args) { //打印代理類 System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, (String)System.getProperties().get("user.dir")); Apple apple = new Apple(); Apple proxy = (Apple) new CglibProxy().createProxy(apple); proxy.secKill(); } }
結果:
代理類的父類信息:com.wzj.cglib.Apple 方法執行日誌開始,方法名爲:secKill 蘋果正在秒殺中... 方法執行日誌結束,方法名爲:secKill
一樣,代理類的信息會打印在工程所在目錄下,如圖:
這裏會有三個類的生成,只有一個是代理類,繼承Apple
,其餘兩個類繼承FastClass
,其中一個class爲生成的代理類中的每一個方法創建了索引,另一個則爲咱們被代理類的全部方法包含其父類的方法創建了索引。
public class Apple$$EnhancerByCGLIB$$3a0529f7 extends Apple implements Factory
public class Apple$$EnhancerByCGLIB$$3a0529f7$$FastClassByCGLIB$$17333818 extends FastClass
public class Apple$$FastClassByCGLIB$$ca2d2eb9 extends FastClass
JDK動態代理的被代理類必需要實現接口。
cglib動態代理是經過繼承被代理類來實現,若是被代理類爲final
字所修飾的非protect與public類
,則無法代理。
性能測試設計了以下5個類JdkDynamicProxyTest
,CglibProxyTest
,Target
,TargetImpl
,ProxyPerformanceTest
package com.wzj.proxy.v10; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * @Author: wzj * @Date: 2020/8/7 21:58 * @Desc: JDK動態代理 */ public class JdkDynamicProxyTest implements InvocationHandler { private Target target; private JdkDynamicProxyTest(Target target) { this.target = target; } public static Target newProxyInstance(Target target) { return (Target) Proxy.newProxyInstance(JdkDynamicProxyTest.class.getClassLoader(), new Class<?>[]{Target.class}, new JdkDynamicProxyTest(target)); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { return method.invoke(target, args); } }
package com.wzj.proxy.v10; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method; /** * @Author: wzj * @Date: 2020/8/7 22:02 * @Desc: Cglib代理測試 */ public class CglibProxyTest implements MethodInterceptor { private CglibProxyTest() { } public static <T extends Target> Target newProxyInstance(Class<T> targetInstanceClazz) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(targetInstanceClazz); enhancer.setCallback(new CglibProxyTest()); return (Target) enhancer.create(); } @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { return methodProxy.invokeSuper(o, objects); } }
package com.wzj.proxy.v10; /** * @Author: wzj * @Date: 2020/8/7 21:54 * @Desc: 被代理類 */ public interface Target { int test(int i); }
package com.wzj.proxy.v10; /** * @Author: wzj * @Date: 2020/8/7 21:57 * @Desc: */ public class TargetImpl implements Target { @Override public int test(int i) { return i + 1; } }
package com.wzj.proxy.v10; import java.util.LinkedHashMap; import java.util.Map; /** * @Author: wzj * @Date: 2020/8/7 21:53 * @Desc: 代理性能測試 */ public class ProxyPerformanceTest { public static void main(String[] args) { //建立測試對象 Target nativeTest = new TargetImpl(); Target dynamicProxy = JdkDynamicProxyTest.newProxyInstance(nativeTest); Target cglibProxy = CglibProxyTest.newProxyInstance(TargetImpl.class); //預熱一下 int preRunCount = 10000; runWithoutMonitor(nativeTest, preRunCount); runWithoutMonitor(cglibProxy, preRunCount); runWithoutMonitor(dynamicProxy, preRunCount); //執行測試 Map<String, Target> tests = new LinkedHashMap<String, Target>(); tests.put("Native ", nativeTest); tests.put("Dynamic ", dynamicProxy); tests.put("Cglib ", cglibProxy); int repeatCount = 3; int runCount = 1000000; runTest(repeatCount, runCount, tests); runCount = 50000000; runTest(repeatCount, runCount, tests); } private static void runTest(int repeatCount, int runCount, Map<String, Target> tests) { System.out.println( String.format("\n===== run test : [repeatCount=%s] [runCount=%s] [java.version=%s] =====", repeatCount, runCount, System.getProperty("java.version"))); for (int i = 0; i < repeatCount; i++) { System.out.println(String.format("\n--------- test : [%s] ---------", (i + 1))); for (String key : tests.keySet()) { runWithMonitor(tests.get(key), runCount, key); } } } private static void runWithoutMonitor(Target target, int runCount) { for (int i = 0; i < runCount; i++) { target.test(i); } } private static void runWithMonitor(Target target, int runCount, String tag) { long start = System.currentTimeMillis(); for (int i = 0; i < runCount; i++) { target.test(i); } long end = System.currentTimeMillis(); System.out.println("[" + tag + "] Total Time:" + (end - start) + "ms"); } }
測試結果:
JDK1.6.0_43
===== run test : [repeatCount=3] [runCount=1000000] [java.version=1.6.0_43] ===== --------- test : [1] --------- [Native ] Total Time:4ms [Dynamic ] Total Time:57ms [Cglib ] Total Time:59ms --------- test : [2] --------- [Native ] Total Time:7ms [Dynamic ] Total Time:34ms [Cglib ] Total Time:40ms --------- test : [3] --------- [Native ] Total Time:6ms [Dynamic ] Total Time:27ms [Cglib ] Total Time:42ms ===== run test : [repeatCount=3] [runCount=50000000] [java.version=1.6.0_43] ===== --------- test : [1] --------- [Native ] Total Time:358ms [Dynamic ] Total Time:985ms [Cglib ] Total Time:1340ms --------- test : [2] --------- [Native ] Total Time:230ms [Dynamic ] Total Time:564ms [Cglib ] Total Time:817ms --------- test : [3] --------- [Native ] Total Time:177ms [Dynamic ] Total Time:404ms [Cglib ] Total Time:726ms
JDK1.7.0_79
===== run test : [repeatCount=3] [runCount=1000000] [java.version=1.7.0_79] ===== --------- test : [1] --------- [Native ] Total Time:0ms [Dynamic ] Total Time:40ms [Cglib ] Total Time:59ms --------- test : [2] --------- [Native ] Total Time:10ms [Dynamic ] Total Time:10ms [Cglib ] Total Time:70ms --------- test : [3] --------- [Native ] Total Time:0ms [Dynamic ] Total Time:30ms [Cglib ] Total Time:50ms ===== run test : [repeatCount=3] [runCount=50000000] [java.version=1.7.0_79] ===== --------- test : [1] --------- [Native ] Total Time:500ms [Dynamic ] Total Time:933ms [Cglib ] Total Time:1490ms --------- test : [2] --------- [Native ] Total Time:262ms [Dynamic ] Total Time:595ms [Cglib ] Total Time:781ms --------- test : [3] --------- [Native ] Total Time:172ms [Dynamic ] Total Time:406ms [Cglib ] Total Time:693ms
JDK1.8.0_161
===== run test : [repeatCount=3] [runCount=1000000] [java.version=1.8.0_161] ===== --------- test : [1] --------- [Native ] Total Time:6ms [Dynamic ] Total Time:40ms [Cglib ] Total Time:94ms --------- test : [2] --------- [Native ] Total Time:5ms [Dynamic ] Total Time:23ms [Cglib ] Total Time:30ms --------- test : [3] --------- [Native ] Total Time:11ms [Dynamic ] Total Time:20ms [Cglib ] Total Time:28ms ===== run test : [repeatCount=3] [runCount=50000000] [java.version=1.8.0_161] ===== --------- test : [1] --------- [Native ] Total Time:273ms [Dynamic ] Total Time:990ms [Cglib ] Total Time:1419ms --------- test : [2] --------- [Native ] Total Time:241ms [Dynamic ] Total Time:562ms [Cglib ] Total Time:851ms --------- test : [3] --------- [Native ] Total Time:210ms [Dynamic ] Total Time:551ms [Cglib ] Total Time:855ms
筆者機器cpu是英特爾酷睿I5,4核,從測試結果看出,JDK動態代理的速度已經比CGLib動態代理的速度要稍微快一點,並不像一些資料或博客所說的那樣,cglib比jdk快幾倍。
筆者用的Spring版本是5.1.6,其中有個建立代理的類爲DefaultAopProxyFactory
,部分源碼以下
public class DefaultAopProxyFactory implements AopProxyFactory, Serializable { @Override public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException { //config.isOptimize() 是否對代理類的生成使用策略優化 其做用是和isProxyTargetClass是同樣的 默認爲false //config.isProxyTargetClass() 是否使用Cglib的方式建立代理對象 默認爲false //hasNoUserSuppliedProxyInterfaces目標類是否有接口存在 且只有一個接口的時候接口類型不是 //SpringProxy類型 if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) { //上面的三個方法有一個爲true的話,則進入到這裏 Class<?> targetClass = config.getTargetClass(); if (targetClass == null) { throw new AopConfigException("TargetSource cannot determine target class: " + "Either an interface or a target is required for proxy creation."); } //判斷目標類是不是接口,若是目標類是接口的話,則使用JDK的方式生成代理對象 //若是目標類是Proxy類型,則仍是使用JDK的方式生成代理對象 if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) { return new JdkDynamicAopProxy(config); } //配置了使用Cglib進行動態代理 或者目標類沒有接口 那麼使用Cglib的方式建立代理對象 return new ObjenesisCglibAopProxy(config); } else { //上面的三個方法沒有一個爲true 那使用JDK的提供的代理方式生成代理對象 return new JdkDynamicAopProxy(config); } }
可見SpringAOP底層源碼在實現時採用了JDK與Cglib兩種動態代理方式
本篇主要講了代理的好處,即對被代理類無入侵,同時又可使原目標類功能加強;接着講述了靜態代理的迭代演進過程,以及靜態代理與動態代理的主要技術實現、與區別,最後經過Spring源碼分析了底層所用的代理技術,後續將深刻源碼分析動態代理是如何一步一步生成代理類的整個過程,敬請期待。
附:githup源碼下載地址:https://github.com/wuzhujun2006/design-patterns