• <menu id="gyiem"><menu id="gyiem"></menu></menu>
  • <menu id="gyiem"><code id="gyiem"></code></menu>

    Java設計模式(七) Spring AOP JDK動態代理 vs. Cglib

    原創文章,轉載請務必將下面這段話置于文章開頭處(保留超鏈接)。
    本文轉發自技術世界原文鏈接 http://www.luozeyang.com/design_pattern/dynamic_proxy_cglib/

    靜態代理 VS. 動態代理

    靜態代理,是指程序運行前就已經存在了代理類的字節碼文件,代理類和被代理類的關系在運行前就已經確定。

    上一篇文章《Java設計模式(六) 代理模式 VS. 裝飾模式》所講的代理為靜態代理。如上文所講,一個靜態代理類只代理一個具體類。如果需要對實現了同一接口的不同具體類作代理,靜態代理需要為每一個具體類創建相應的代理類。

    動態代理類的字節碼是在程序運行期間動態生成,所以不存在代理類的字節碼文件。代理類和被代理類的關系是在程序運行時確定的。

    JDK動態代理

    JDK從1.3開始引入動態代理。可通過java.lang.reflect.Proxy類的靜態方法Proxy.newProxyInstance動態創建代理類和實例。并且由它動態創建出來的代理類都是Proxy類的子類。

    定義代理行為

    代理類往往會在代理對象業務邏輯前后增加一些功能性的行為,如使用事務或者打印日志。本文把這些行為稱之為代理行為

    使用JDK動態代理,需要創建一個實現java.lang.reflect.InvocationHandler接口的類,并在該類中定義代理行為。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    package com.jasongj.proxy.jdkproxy;

    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;

    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;

    public class SubjectProxyHandler implements InvocationHandler {

    private static final Logger LOG = LoggerFactory.getLogger(SubjectProxyHandler.class);

    private Object target;

    @SuppressWarnings("rawtypes")
    public SubjectProxyHandler(Class clazz) {
    try {
    this.target = clazz.newInstance();
    } catch (InstantiationException | IllegalAccessException ex) {
    LOG.error("Create proxy for {} failed", clazz.getName());
    }
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    preAction();
    Object result = method.invoke(target, args);
    postAction();
    LOG.info("Proxy class name {}", proxy.getClass().getName());
    return result;
    }

    private void preAction() {
    LOG.info("SubjectProxyHandler.preAction()");
    }

    private void postAction() {
    LOG.info("SubjectProxyHandler.postAction()");
    }

    }

    從上述代碼中可以看到,被代理對象的類對象作為參數傳給了構造方法,原因如下

    • 如上文所述,動態代理可以代理多種類,而且具體代理哪種類并非臺靜態代理那樣編譯時確定,而是在運行時指定
    • 之所以不傳被代理類的實例而是傳類對象,是為了與上文《Java設計模式(六) 代理模式 VS. 裝飾模式》吻合——被代理對象不由客戶端創建而由代理創建,客戶端甚至都不需要知道被代理對象的存在。具體傳被代理類的實例還是傳類對象,并無嚴格規定
    • 一些講JDK動態代理的例子會專門使用一個public方法去接收該參數。但筆者個人認為最好不要在具體類中實現未出現在接口定義中的public方法

    注意,SubjectProxyHandler定義的是代理行為而非代理類本身。實際上代理類及其實例是在運行時通過反射動態創建出來的。

    JDK動態代理使用方式

    代理行為定義好后,先實例化SubjectProxyHandler(在構造方法中指明被代理類),然后通過Proxy.newProxyInstance動態創建代理類的實例。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    package com.jasongj.client;

    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Proxy;

    import com.jasongj.proxy.jdkproxy.SubjectProxyHandler;
    import com.jasongj.subject.ConcreteSubject;
    import com.jasongj.subject.ISubject;

    public class JDKDynamicProxyClient {

    public static void main(String[] args) {
    InvocationHandler handler = new SubjectProxyHandler(ConcreteSubject.class);
    ISubject proxy =
    (ISubject) Proxy.newProxyInstance(JDKDynamicProxyClient.class.getClassLoader(),
    new Class[] {ISubject.class}, handler);
    proxy.action();
    }

    }

    從上述代碼中也可以看到,Proxy.newProxyInstance的第二個參數是類對象數組,也就意味著被代理對象可以實現多個接口。

    運行結果如下

    1
    2
    3
    4
    SubjectProxyHandler.preAction()
    ConcreteSubject action()
    SubjectProxyHandler.postAction()
    Proxy class name com.sun.proxy.$Proxy18

    從上述結果可以看到,定義的代理行為順利的加入到了執行邏輯中。同時,最后一行日志說明了代理類的類名是com.sun.proxy.$Proxy18,驗證了上文的論點——SubjectProxyHandler定義的是代理行為而非代理類本身,代理類及其實例是在運行時通過反射動態創建出來的。

    生成的動態代理類

    Proxy.newProxyInstance是通過靜態方法ProxyGenerator.generateProxyClass動態生成代理類的字節碼的。為了觀察創建出來的代理類的結構,本文手工調用該方法,得到了代理類的字節碼,并將之輸出到了class文件中。

    1
    byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy18", ConcreteSubject.class.getInterfaces());

    使用反編譯工具可以得到代理類的代碼

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    import com.jasongj.subject.ISubject;
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    import java.lang.reflect.UndeclaredThrowableException;

    public final class $Proxy18 extends Proxy implements ISubject {
    private static Method m1;
    private static Method m2;
    private static Method m0;
    private static Method m3;

    public $Proxy18(InvocationHandler paramInvocationHandler) {
    super(paramInvocationHandler);
    }

    public final boolean equals(Object paramObject) {
    try {
    return ((Boolean) this.h.invoke(this, m1, new Object[] {paramObject})).booleanValue();
    } catch (Error | RuntimeException localError) {
    throw localError;
    } catch (Throwable localThrowable) {
    throw new UndeclaredThrowableException(localThrowable);
    }
    }

    public final String toString() {
    try {
    return (String) this.h.invoke(this, m2, null);
    } catch (Error | RuntimeException localError) {
    throw localError;
    } catch (Throwable localThrowable) {
    throw new UndeclaredThrowableException(localThrowable);
    }
    }

    public final int hashCode() {
    try {
    return ((Integer) this.h.invoke(this, m0, null)).intValue();
    } catch (Error | RuntimeException localError) {
    throw localError;
    } catch (Throwable localThrowable) {
    throw new UndeclaredThrowableException(localThrowable);
    }
    }

    public final void action() {
    try {
    this.h.invoke(this, m3, null);
    return;
    } catch (Error | RuntimeException localError) {
    throw localError;
    } catch (Throwable localThrowable) {
    throw new UndeclaredThrowableException(localThrowable);
    }
    }

    static {
    try {
    m1 = Class.forName("java.lang.Object").getMethod("equals",
    new Class[] {Class.forName("java.lang.Object")});
    m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
    m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
    m3 = Class.forName("com.jasongj.subject.ISubject").getMethod("action", new Class[0]);
    } catch (NoSuchMethodException localNoSuchMethodException) {
    throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
    } catch (ClassNotFoundException localClassNotFoundException) {
    throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
    }
    }
    }

    從該類的聲明中可以看到,繼承了Proxy類,并實現了ISubject接口。驗證了上文中的論點——所有生成的動態代理類都是Proxy類的子類。同時也解釋了為什么JDK動態代理只能代理實現了接口的類——Java不支持多繼承,代理類已經繼承了Proxy類,無法再繼承其它類。

    同時,代理類重寫了hashCode,toString和equals這三個從Object繼承下來的接口,通過InvocationHandler的invoke方法去實現。除此之外,該代理類還實現了ISubject接口的action方法,也是通過InvocationHandler的invoke方法去實現。這就解釋了示例代碼中代理行為是怎樣被調用的。

    前文提到,被代理類可以實現多個接口。從代理類代碼中可以看到,代理類是通過InvocationHandler的invoke方法去實現代理接口的。所以當被代理對象實現了多個接口并且希望對不同接口實施不同的代理行為時,應該在SubjectProxyHandler類,也即代理行為定義類中,通過判斷方法名,實現不同的代理行為。

    cglib

    cglib介紹

    cglib是一個強大的高性能代碼生成庫,它的底層是通過使用一個小而快的字節碼處理框架ASM(Java字節碼操控框架)來轉換字節碼并生成新的類。

    cglib方法攔截器

    使用cglib實現動態代理,需要在MethodInterceptor實現類中定義代理行為。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    package com.jasongj.proxy.cglibproxy;

    import java.lang.reflect.Method;

    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;

    import net.sf.cglib.proxy.MethodInterceptor;
    import net.sf.cglib.proxy.MethodProxy;

    public class SubjectInterceptor implements MethodInterceptor {

    private static final Logger LOG = LoggerFactory.getLogger(SubjectInterceptor.class);

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy)
    throws Throwable {
    preAction();
    Object result = proxy.invokeSuper(obj, args);
    postAction();
    return result;
    }

    private void preAction() {
    LOG.info("SubjectProxyHandler.preAction()");
    }

    private void postAction() {
    LOG.info("SubjectProxyHandler.postAction()");
    }

    }

    代理行為在intercept方法中定義,同時通過getInstance方法(該方法名可以自定義)獲取動態代理的實例,并且可以通過向該方法傳入類對象指定被代理對象的類型。

    cglib使用方式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    package com.jasongj.client;

    import com.jasongj.proxy.cglibproxy.SubjectInterceptor;
    import com.jasongj.subject.ConcreteSubject;
    import com.jasongj.subject.ISubject;

    import net.sf.cglib.proxy.Enhancer;
    import net.sf.cglib.proxy.MethodInterceptor;

    public class CgLibProxyClient {

    public static void main(String[] args) {
    MethodInterceptor methodInterceptor = new SubjectInterceptor();
    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(ConcreteSubject.class);
    enhancer.setCallback(methodInterceptor);
    ISubject subject = (ISubject)enhancer.create();
    subject.action();
    }

    }

    性能測試

    分別使用JDK動態代理創建代理對象1億次,并分別執行代理對象方法10億次,代碼如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    package com.jasongj.client;

    import java.io.IOException;
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Proxy;

    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;

    import com.jasongj.proxy.cglibproxy.SubjectInterceptor;
    import com.jasongj.proxy.jdkproxy.SubjectProxyHandler;
    import com.jasongj.subject.ConcreteSubject;
    import com.jasongj.subject.ISubject;

    import net.sf.cglib.proxy.Enhancer;
    import net.sf.cglib.proxy.MethodInterceptor;

    public class DynamicProxyPerfClient {

    private static final Logger LOG = LoggerFactory.getLogger(DynamicProxyPerfClient.class);
    private static int creation = 100000000;
    private static int execution = 1000000000;

    public static void main(String[] args) throws IOException {
    testJDKDynamicCreation();
    testJDKDynamicExecution();
    testCglibCreation();
    testCglibExecution();
    }

    private static void testJDKDynamicCreation() {
    long start = System.currentTimeMillis();
    for (int i = 0; i < creation; i++) {
    InvocationHandler handler = new SubjectProxyHandler(ConcreteSubject.class);
    Proxy.newProxyInstance(DynamicProxyPerfClient.class.getClassLoader(),
    new Class[] {ISubject.class}, handler);
    }
    long stop = System.currentTimeMillis();
    LOG.info("JDK creation time : {} ms", stop - start);
    }

    private static void testJDKDynamicExecution() {
    long start = System.currentTimeMillis();
    InvocationHandler handler = new SubjectProxyHandler(ConcreteSubject.class);
    ISubject subject =
    (ISubject) Proxy.newProxyInstance(DynamicProxyPerfClient.class.getClassLoader(),
    new Class[] {ISubject.class}, handler);
    for (int i = 0; i < execution; i++) {
    subject.action();
    }
    long stop = System.currentTimeMillis();
    LOG.info("JDK execution time : {} ms", stop - start);
    }

    private static void testCglibCreation() {
    long start = System.currentTimeMillis();
    for (int i = 0; i < creation; i++) {
    MethodInterceptor methodInterceptor = new SubjectInterceptor();
    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(ConcreteSubject.class);
    enhancer.setCallback(methodInterceptor);
    enhancer.create();
    }
    long stop = System.currentTimeMillis();
    LOG.info("cglib creation time : {} ms", stop - start);
    }

    private static void testCglibExecution() {
    MethodInterceptor methodInterceptor = new SubjectInterceptor();
    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(ConcreteSubject.class);
    enhancer.setCallback(methodInterceptor);
    ISubject subject = (ISubject) enhancer.create();
    long start = System.currentTimeMillis();
    for (int i = 0; i < execution; i++) {
    subject.action();
    }
    long stop = System.currentTimeMillis();
    LOG.info("cglib execution time : {} ms", stop - start);
    }

    }

    結果如下

    1
    2
    3
    4
    JDK creation time : 9924 ms
    JDK execution time : 3472 ms
    cglib creation time : 16108 ms
    cglib execution time : 6309 ms

    該性能測試表明,JDK動態代理創建代理對象速度是cglib的約1.6倍,并且JDK創建出的代理對象執行速度是cglib代理對象執行速度的約1.8倍

    JDK動態代理與cglib對比

    • 字節碼創建方式:JDK動態代理通過JVM實現代理類字節碼的創建,cglib通過ASM創建字節碼
    • 對被代理對象的要求:JDK動態代理要求被代理對象實現接口,cglib要求被代理對象未被final修飾
    • 代理對象創建速度:JDK動態代理創建代理對象速度比cglib快
    • 代理對象執行速度:JDK動態代理代理對象執行速度比cglib快

    本文所有示例代理均可從作者Github下載

    Java設計模式系列

    郭俊 Jason wechat
    歡迎關注作者微信公眾號【大數據架構】
    您的贊賞將支持作者繼續原創分享
    速赢彩app 陵水 | 大理 | 南阳 | 滕州 | 高雄 | 济南 | 临沧 | 黑龙江哈尔滨 | 温州 | 承德 | 丹阳 | 芜湖 | 佳木斯 | 甘孜 | 泰安 | 昭通 | 宜春 | 克拉玛依 | 诸城 | 丽水 | 广西南宁 | 庄河 | 石狮 | 盘锦 | 红河 | 漳州 | 山西太原 | 台湾台湾 | 通辽 | 宁波 | 阿坝 | 松原 | 安徽合肥 | 廊坊 | 鄂尔多斯 | 六盘水 | 铜陵 | 兴安盟 | 和县 | 库尔勒 | 项城 | 琼中 | 燕郊 | 嘉兴 | 乌海 | 塔城 | 临汾 | 垦利 | 库尔勒 | 海安 | 神农架 | 泰安 | 甘南 | 青海西宁 | 包头 | 松原 | 苍南 | 慈溪 | 陇南 | 金昌 | 南平 | 陵水 | 遂宁 | 清徐 | 河北石家庄 | 万宁 | 德宏 | 如皋 | 塔城 | 昭通 | 神木 | 山西太原 | 威海 | 溧阳 | 垦利 | 台中 | 济宁 | 晋江 | 福建福州 | 安康 | 兴安盟 | 宜宾 | 三明 | 贵州贵阳 | 安顺 | 承德 | 北海 | 博尔塔拉 | 梧州 | 阜阳 | 嘉善 | 上饶 | 海西 | 那曲 | 日照 | 简阳 | 汕尾 | 锡林郭勒 | 项城 | 朔州 | 海南 | 龙岩 | 秦皇岛 | 佛山 | 钦州 | 连云港 | 喀什 | 青州 | 常州 | 鄢陵 | 昆山 | 绵阳 | 通化 |