一文搞定动态代理的两种方式

代理模式

生活处处可见代理,我们经常会碰到各种代理,比如我们常见的微商其实就是一种代理商,他们直接负责和客户交互,但是其实真正卖的东西还是在厂家那里,这里微商就是厂家的一种代理,当然一般微商都是层层代理;那么在计算机中的代理概念和我们日常生活中的代理其实概念一致,客户端不直接与真正的对象交互,而是与目标对象的中间代理交互,这样代理可以在真正行为前后做一些特殊的动作(保证扩展红牛的同时还不对原有代码逻辑进行侵入)。

image.png

静态代理

有动态代理,那自然肯定也有静态代理,先来看一下什么是静态代理?

假设我们需要统计方法执行的耗时,为了不保证对原有代码进行侵入,我们选择了通过代理类增强功能来实现,起初我们通过如下方式去实现:

public interface Service {
    void operate();
}
public class RealServiceImpl implements Service{
    @Override
    public void operate() {
        System.out.println("执行方法...");
    }
}
public class ProxyServiceImpl implements Service {
    private Service service;

    public ProxyServiceImpl(Service service) {
        this.service = service;
    }

    @Override
    public void operate() {
        long startTime = System.currentTimeMillis();
        service.operate();
        System.out.println("执行耗时:" + (System.currentTimeMillis() - startTime));
    }
}
public class Client {
    public static void main(String[] args) {
        Service realService = new RealServiceImpl();
        Service proxy = new ProxyServiceImpl(realService);
        proxy.operate();
    }
}

上述就是静态代理的实现,通过为Service创建代理实现类,代理实现类持有真实对象的引用,客户端不直接与真实对象交互,而是调用代理类,这样使得代理类可以在真实行为前后做一些扩展操作。这些代理类在编译器就被确定,从而称之为静态代理。

但是如果我们有很多这样的Service,是不是需要为每一个Service都创建一个代理类呢?那这样我们是不是就会有很庞大的代理类;其次,假如后续需要新增方法,那么是不是所有的代理类都要进行更改?那么这些就成为了静态代理的弊端。

动态代理

与静态代理相比,动态的意思自然就是在运行时生成代理类,需要注意的是运行时生成的代理并像静态代理那样生成很多class文件,而是通过在运行时生成字节码加载到JVM中。动态代理常见的有JDK动态代理CGLIB两种方式,他们的实现原理的不一样也带来了一些使用场景的限制。

JDK动态代理

首先上一段JDK动态代理的例子:
动态代理实现扩展的关键在于实现InvocationHandler接口:

@Getter
public class DynamicProxyServiceImpl implements InvocationHandler {
    private Object targetObject;

    public DynamicProxyServiceImpl(Object targetObject) {
        this.targetObject = targetObject;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        long startTime = System.currentTimeMillis();
        method.invoke(targetObject, args);
        System.out.println("方法执行耗时:" + (System.currentTimeMillis() - startTime));
        return null;
    }
}

然后通过java.lang.reflect.Proxy#newProxyInstance来生成相应的代理类

public class Client {
    public static void main(String[] args) {
        Service realService = new RealServiceImpl();
        
        DynamicProxyServiceImpl dynamicProxyService = new DynamicProxyServiceImpl(realService);
        
        Service proxyInstance = (Service) Proxy
            .newProxyInstance(dynamicProxyService.getClass().getClassLoader(), realService.getClass().getInterfaces(),
                dynamicProxyService);
        proxyInstance.operate();

    }
}
    

CGLib动态代理

CGLIB全称Code Generation Library,是一个强大的,高性能,高质量的基于ASM字节码生产类库,它可以在运行期扩展Java类与实现Java接口。

public class ProxyMethodInterceptor implements MethodInterceptor {
    /**
     * @param proxyObj 代理生产的对象
     * @param method  被代理对象的方法
     * @param args 方法入参
     * @param methodProxy 代理对象的方法
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Object proxyObj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        long startTime = System.currentTimeMillis();
        methodProxy.invokeSuper(proxyObj, args);
        System.out.println("方法耗时:" + (System.currentTimeMillis() - startTime));
        return null;
    }
}
public class Client {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(RealServiceImpl.class);
        enhancer.setCallback(new ProxyMethodInterceptor());
        RealServiceImpl realService = (RealServiceImpl) enhancer.create();
        realService.operate();

    }
}

JDK动态代理和CGLIB动态代理的原理和区别

首先来看看动态代理生成的Class和CGLIB生成的Class有什么不同?

JDK动态代理原理

通过执行下述代码可以得到JDK动态代理生成的class文件:

byte[] bytes = ProxyGenerator.generateProxyClass("ProxyServiceImpl", new Class[] { Service.class });
FileOutputStream outputStream = new FileOutputStream(new File("/Users/zhuyu/ProxyServiceImpl.class"));
outputStream.write(bytes);
outputStream.flush();
outputStream.close();

生成的class长这样:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

import com.cf.springboot.proxy.Service;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class ProxyServiceImpl extends Proxy implements Service {
    private static Method m1;
    private static Method m2;
    private static Method m0;
    private static Method m3;

    public ProxyServiceImpl(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 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);
        }
    }

    public final void operate() throws  {
        try {
            super.h.invoke(this, m3, (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"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
            m3 = Class.forName("com.cf.springboot.proxy.Service").getMethod("operate");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

可以看到生成的代理类实现了我们目标接口,并且继承了Proxy类,在实现的每个方法中,调用我们的InvocationHandler去做处理,进而实现了增强的目的。通过两个关键字总结:反射接口实现

CGLIB动态代理

通过如下代码生成CGLIB产生的class文件:

System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/Users/");
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(RealServiceImpl.class);
enhancer.setCallback(new ProxyMethodInterceptor());
RealServiceImpl realService = (RealServiceImpl) enhancer.create();

生成的class如下:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.cf.springboot.proxy;

import java.lang.reflect.Method;
import net.sf.cglib.core.ReflectUtils;
import net.sf.cglib.core.Signature;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.Factory;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class RealServiceImpl$$EnhancerByCGLIB$$b6ece686 extends RealServiceImpl implements Factory {
    private boolean CGLIB$BOUND;
    public static Object CGLIB$FACTORY_DATA;
    private static final ThreadLocal CGLIB$THREAD_CALLBACKS;
    private static final Callback[] CGLIB$STATIC_CALLBACKS;
    private MethodInterceptor CGLIB$CALLBACK_0;
    private static Object CGLIB$CALLBACK_FILTER;
    private static final Method CGLIB$operate$0$Method;
    private static final MethodProxy CGLIB$operate$0$Proxy;
    private static final Object[] CGLIB$emptyArgs;
    private static final Method CGLIB$equals$1$Method;
    private static final MethodProxy CGLIB$equals$1$Proxy;
    private static final Method CGLIB$toString$2$Method;
    private static final MethodProxy CGLIB$toString$2$Proxy;
    private static final Method CGLIB$hashCode$3$Method;
    private static final MethodProxy CGLIB$hashCode$3$Proxy;
    private static final Method CGLIB$clone$4$Method;
    private static final MethodProxy CGLIB$clone$4$Proxy;

    static void CGLIB$STATICHOOK1() {
        CGLIB$THREAD_CALLBACKS = new ThreadLocal();
        CGLIB$emptyArgs = new Object[0];
        Class var0 = Class.forName("com.cf.springboot.proxy.RealServiceImpl$$EnhancerByCGLIB$$b6ece686");
        Class var1;
        Method[] var10000 = ReflectUtils.findMethods(new String[]{"equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;"}, (var1 = Class.forName("java.lang.Object")).getDeclaredMethods());
        CGLIB$equals$1$Method = var10000[0];
        CGLIB$equals$1$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$1");
        CGLIB$toString$2$Method = var10000[1];
        CGLIB$toString$2$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/String;", "toString", "CGLIB$toString$2");
        CGLIB$hashCode$3$Method = var10000[2];
        CGLIB$hashCode$3$Proxy = MethodProxy.create(var1, var0, "()I", "hashCode", "CGLIB$hashCode$3");
        CGLIB$clone$4$Method = var10000[3];
        CGLIB$clone$4$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/Object;", "clone", "CGLIB$clone$4");
        CGLIB$operate$0$Method = ReflectUtils.findMethods(new String[]{"operate", "()V"}, (var1 = Class.forName("com.cf.springboot.proxy.RealServiceImpl")).getDeclaredMethods())[0];
        CGLIB$operate$0$Proxy = MethodProxy.create(var1, var0, "()V", "operate", "CGLIB$operate$0");
    }

    final void CGLIB$operate$0() {
        super.operate();
    }

    public final void operate() {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        if (var10000 != null) {
            var10000.intercept(this, CGLIB$operate$0$Method, CGLIB$emptyArgs, CGLIB$operate$0$Proxy);
        } else {
            super.operate();
        }
    }

省略一大批代码。。。

从上面class看到最终生成的代理类是继承了我们目标实现类,覆写的目标实现类方法中,调用我们传入的MethodInterceptor做处理,关键字总结:字节码生成类继承

最后一句话简单描述原理: JDK动态代理是通过以反射的方式实现接口的方式进行增强,而CGLIB是以增强字节码通过继承的方式实现增强,实现原理的不同从而也决定了他们适用于不同的场景中:

适用场景

1)比如如果要对一个类进行增强,但是这个类并没有一个顶层抽象接口,那么此时只能选择CGLIB通过继承的方式去进行增强

2)加入我们要增强的方式被final进行了修饰,那么由final关键字语意可知,无法此方式将无法被重写,所以CGLIB就没法满足我们的要求了

性能对比

实现原理不同,可能造成性能的差异,那么究竟是通过反射实现的JDK动态代理性能好还是通过字节码增强技术实现的CGLIB性能好呢?

这里已经有博主实操分析过,可直接转至https://blog.csdn.net/xlgen157387/article/details/82497594了解。

参考

[1] https://refactoringguru.cn/design-patterns/proxy
[2] https://zhangzw.com/posts/20200120.html

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,794评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,050评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,587评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,861评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,901评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,898评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,832评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,617评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,077评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,349评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,483评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,199评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,824评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,442评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,632评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,474评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,393评论 2 352

推荐阅读更多精彩内容