深入浅出jdk动态代理和cglib动态代理

深入浅出jdk动态代理和cglib动态代理

代理模式

生活中我们可以见到各种各样的代理,比如租房子找中介,比如驾校招生代理。他们有一个共同点就是他们只算作中间人,但不是最后真正的执行者,房子中介租房的时候,房子主人并不是他,驾校招生代理在招人的时候,只是登记你的信息,具体去哪练车,还是要看驾校的,这就是代理通俗上描述的样子。在代理设计模式中,其实很类似。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9nnvh19l-1598800074310)(media/15986937579714/15986949072896.jpg)]

某个接口定义了一些方法,有一个实现类实现了它,然后有个代理类也实现了这个接口,并持有实现类实例,代理类的接口实现本质上是调用实现类的逻辑,在调用前后可以增加一些逻辑,也可以控制是否调用实现类。

代码实现

接口类:

/**
 * 接口类
 */
public interface Door {

    void open();
}

实现类:

/**
 * 实现类
 */
public class DoorImpl implements Door {

    @Override
    public void open() {

        System.out.println("开门...");
    }
}

代理类:

/**
 * 代理类
 */
public class DoorProxy implements Door{

    //持有接口Door的一个实例
    private Door door;

    public DoorProxy(Door door) {
        this.door = door;
    }

    @Override
    public void open() {
        //这里可以在调用真实逻辑前添加一些逻辑
        System.out.println("插钥匙...");
        door.open();
        //这里可以在调用真实逻辑后添加一些逻辑
        System.out.println("拔钥匙...");
    }
}

使用:

/**
 * 使用者
 */
public class Main {

    public static void main(String[] args) {

        Door door = new DoorProxy(new DoorImpl());
        door.open();
    }
}

输出:

Connected to the target VM, address: '127.0.0.1:50052', transport: 'socket'
插钥匙...
开门...
拔钥匙...
Disconnected from the target VM, address: '127.0.0.1:50052', transport: 'socket'

Process finished with exit code 0

这样一个代理模式就演示完成了。但是这中实现有两个弊端

  1. 有多少个需要代理的对象,就要创建多少个Proxy类,如果需要代理的对象很多,创建这些代理类就是个不小的工作
  2. 假设实现类又实现了另外的接口,代理类中要想使用,必须再持有另一个实现类实例,这样代理类中持有了两个实例,其实是一样的,只是接口不同,冗余
    于是JDK给出了动态代理,就是动态生成字节码。然后加载到内存中,一切都是自动的,只需要写调用真实逻辑前后我们需要增加的逻辑即可。

JDK动态代理

JDK动态代理可以动态生成字节码,调用真实逻辑的方法需要我们自己写。

public class Main {

    public static void main(String[] args) {

        //我们接口的实现实例
        Door doorImpl = new DoorImpl();

        Door door = (Door) Proxy.newProxyInstance(doorImpl.getClass().getClassLoader(), doorImpl.getClass().getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //调用真实逻辑前的一些处理
                System.out.println("插钥匙...");
                //调用真实逻辑
                Object re = method.invoke(doorImpl,args);
                //调用真实逻辑后的一些处理
                System.out.println("拔钥匙...");
                return re;
            }
        });
        door.open();
    }
}

首先是new出我们的实现类,因为这个类里面是我们的实现逻辑,然后调用

java.lang.reflectProxy.newProxyInstance(ClassLoader loader,
    Class<?>[] interfaces,
    InvocationHandler h)
    throws IllegalArgumentException

这个方法生成一个代理类,强制转换成为Door的实例,就可以使用了。这里解释下这三个参数。ClassLoader loader这个参数是接口实现类的类加载器,可以通过DoorImpl.getClass().getClassLoader()获取到。Class<?>[] interfaces这个参数是实现类所实现的接口,这里可以传入new Class[]{Door.class},表示当前实现类实现了Door这个接口,但是大多数情况下,我们的实现类可能不只实现一个接口,为了更加通用,我们使用反射来获取实现类实现的所有接口。InvocationHandler h这个参数是个接口,里面只有一个方法

public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;

这个方法里面定义了我们怎么控制调用真实逻辑,也就是说在调用真实逻辑前要做的事和调用真实逻辑后要做的事。其中方法的三个参数分别是代理类,实现类的方法,调用实现类方法的参数。上面的这个方法里面逻辑其中Object re = method.invoke(doorImpl,args);是在调用真实逻辑,注意这里面method.invoke的第一个参数是doorImpl而不是proxyObject re = method.invoke(doorImpl,args);前面和后面就是我们要添加的一些逻辑了。

JDK动态代理原理

上面我们运行发现和之前写的代理是同样的效果,那么JDK是怎么做的呢,最好的办法就是撸一遍源码。给出大致过程,不过多贴源码,感兴趣的自己去看吧,贴太多就本末倒置了。

java.lang.reflect.Proxy#newProxyInstance主要逻辑:
1. 克隆接口,检查权限
2. Class<?> cl = getProxyClass0(loader, intfs);这一行是生成代理类
3. return cons.newInstance(new Object[]{h});最后调用了生成类的构造方法,传入了h,这个h就是上面代码中的InvocationHandler实例

继续追踪上面的第2步中的生成细节:

java.lang.reflect.Proxy#getProxyClass0主要逻辑:
1. 检查实现类实现的类个数不能超过655352. return proxyClassCache.get(loader, interfaces);

到这里就可以看出来和缓存有关,继续追:

java.lang.reflect.WeakCache#get主要逻辑:
1. 参数不能为null判断
2. 判断缓存中是否存在,存在返回,不存在则调用java.lang.reflect.Proxy.ProxyClassFactory#apply创建,源码中对应的是这一句:Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));

然后继续追:

java.lang.reflect.Proxy.ProxyClassFactory#apply主要逻辑:
1. 还是一顿检查
2. 拼接class名:包路径$Proxy数字.class
3. byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, interfaces, accessFlags);到这里的时候就是生成字节码了

追到这我们已经看到生成字节码了,返回了一个byte数组,我们想办法拿到这个字节码,具体的生成细节就不去细致去看了,只要看这个类的字节码,就知道大概生成了啥。jdk8及其以前的版本在代码里添加环境变量

System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

然后运行代码即可拿到生成的class文件。我这里拿到了生成的类,然后把它反编译出来:

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

package com.sun.proxy;

import com.company.Door;
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 Door {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    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 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 void open() throws  {
        try {
            super.h.invoke(this, m3, (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"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("com.company.Door").getMethod("open");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

需要注意几个地方:

  1. 当前类是继承自java.lang.reflect.Proxy类,实现了Door接口
  2. 当前类只有一个构造方法,就是参数为InvocationHandler构造方法,然后使用super(h)初始化了父类,现在来看,这个InvocationHandler是不是有点眼熟?在Proxy.newProxyInstance中提到过。
  3. Door中的方法void open()在本类中有实现,只有一行对于我们来说有效的语句:
super.h.invoke(this, m3, (Object[])null);

源码中可以看到m3是哪个方法:

m3 = Class.forName("com.company.Door").getMethod("open");

super.h.invoke(this, m3, (Object[])null);这句中,父类的h是我们传入的内部类,然后它的invoke方法就是调用我们自己定义的那一套逻辑。也就是:

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    //调用真实逻辑前的一些处理
    System.out.println("插钥匙...");
    //调用真实逻辑
    Object re = method.invoke(doorImpl,args);
    //调用真实逻辑后的一些处理
    System.out.println("拔钥匙...");
    return re;
}

此时便真想大白了。JDK帮我们动态生成了一个代理类,类中实现了我们的接口,然后最后调用的还是我们的逻辑。

Spring中的@Transactional问题

有这么一个问题:Spring中的某个service有个两个方法A()B(),在B()方法上面添加了注解@Transactional注解,然后A()方法中调用了this.B(),为什么事务没有开启?我们知道加了@Transactional方法所在的类会被Spring自动生成代理,如无特殊情况,默认是使用JDK动态代理(Spring Boot 2.0默认使用了cglib,Spring还是JDK动态代理),当前类被代理了,A()方法中使用this.B()调用本类中的B()方法,根据上面我们知道,只有调用代理类的B()方法才会走InvocationHandler(Spring帮我们实现了此接口)那一套,当前是service,不是代理后的类,确切的说是根本就没调用到代理后的类中去,所以这中情况下不会自动开启和关闭事务,如果想让B()方法生效怎么办:

  1. A()方法到别的类中,注入当前service,然后调用B()方法
  2. 在当前service中注入自己,你没有看错,就是注入一个自己(这是Spring的循环依赖,Spring会帮我们处理好的),然后在A()方法中使用service.B()调用B()方法即可。

cglib动态代理

另一种动态代理是cglib动态代理,cglib底层采用ASM字节码技术,可以动态修改字节码。示例还是使用Door接口和DoorImpl作为我们的接口和实现类,其中某一种使用cglib手动生成字节码的代码可能是这样的:

/**
 * 使用者
 */
public class Main {

    public static void main(String[] args) {

        //把动态生成的类输出到当前文件夹
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,"./");
        //我们接口的实现实例
        Door doorImpl = new DoorImpl();

        //cglib生成类的朱磊
        Enhancer enhancer = new Enhancer();
        //设置类加载器
        enhancer.setClassLoader(doorImpl.getClass().getClassLoader());
        //设置实现类
        enhancer.setSuperclass(doorImpl.getClass());
        //设置在原来的基础上添加的逻辑
        enhancer.setCallback(new MethodInterceptor() {
            @Override
            public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {

                //在之前的逻辑前添加逻辑
                System.out.println("插钥匙...");
                //执行真实逻辑 调用的是父类的方法
                Object object = proxy.invokeSuper(obj,args);
                //在之前的逻辑执行后添加逻辑
                System.out.println("拔钥匙...");
                return object;
            }
        });

        //获取动态生成的类
        Door door = (Door) enhancer.create();
        door.open();
    }
}

输出是这样的:

Connected to the target VM, address: '127.0.0.1:55007', transport: 'socket'
CGLIB debugging enabled, writing to './'
插钥匙...
开门...
拔钥匙...
Disconnected from the target VM, address: '127.0.0.1:55007', transport: 'socket'

Process finished with exit code 0

然后在我们当前文件夹下,生成了5个类,我们找到继承自DoorImpl的那个类:

public class DoorImpl$$EnhancerByCGLIB$$850acfc8 extends DoorImpl 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$open$0$Method;
    private static final MethodProxy CGLIB$open$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.company.DoorImpl$$EnhancerByCGLIB$$850acfc8");
        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$open$0$Method = ReflectUtils.findMethods(new String[]{"open", "()V"}, (var1 = Class.forName("com.company.DoorImpl")).getDeclaredMethods())[0];
        CGLIB$open$0$Proxy = MethodProxy.create(var1, var0, "()V", "open", "CGLIB$open$0");
    }

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

    public final void open() {
        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$open$0$Method, CGLIB$emptyArgs, CGLIB$open$0$Proxy);
        } else {
            super.open();
        }
    }

    final boolean CGLIB$equals$1(Object var1) {
        return super.equals(var1);
    }

    public final boolean equals(Object var1) {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        if (var10000 != null) {
            Object var2 = var10000.intercept(this, CGLIB$equals$1$Method, new Object[]{var1}, CGLIB$equals$1$Proxy);
            return var2 == null ? false : (Boolean)var2;
        } else {
            return super.equals(var1);
        }
    }

    final String CGLIB$toString$2() {
        return super.toString();
    }

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

        return var10000 != null ? (String)var10000.intercept(this, CGLIB$toString$2$Method, CGLIB$emptyArgs, CGLIB$toString$2$Proxy) : super.toString();
    }

    final int CGLIB$hashCode$3() {
        return super.hashCode();
    }

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

        if (var10000 != null) {
            Object var1 = var10000.intercept(this, CGLIB$hashCode$3$Method, CGLIB$emptyArgs, CGLIB$hashCode$3$Proxy);
            return var1 == null ? 0 : ((Number)var1).intValue();
        } else {
            return super.hashCode();
        }
    }

    final Object CGLIB$clone$4() throws CloneNotSupportedException {
        return super.clone();
    }

    protected final Object clone() throws CloneNotSupportedException {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        return var10000 != null ? var10000.intercept(this, CGLIB$clone$4$Method, CGLIB$emptyArgs, CGLIB$clone$4$Proxy) : super.clone();
    }

    public static MethodProxy CGLIB$findMethodProxy(Signature var0) {
        String var10000 = var0.toString();
        switch(var10000.hashCode()) {
        case -1263249173:
            if (var10000.equals("open()V")) {
                return CGLIB$open$0$Proxy;
            }
            break;
        case -508378822:
            if (var10000.equals("clone()Ljava/lang/Object;")) {
                return CGLIB$clone$4$Proxy;
            }
            break;
        case 1826985398:
            if (var10000.equals("equals(Ljava/lang/Object;)Z")) {
                return CGLIB$equals$1$Proxy;
            }
            break;
        case 1913648695:
            if (var10000.equals("toString()Ljava/lang/String;")) {
                return CGLIB$toString$2$Proxy;
            }
            break;
        case 1984935277:
            if (var10000.equals("hashCode()I")) {
                return CGLIB$hashCode$3$Proxy;
            }
        }

        return null;
    }

    public DoorImpl$$EnhancerByCGLIB$$850acfc8() {
        CGLIB$BIND_CALLBACKS(this);
    }

    public static void CGLIB$SET_THREAD_CALLBACKS(Callback[] var0) {
        CGLIB$THREAD_CALLBACKS.set(var0);
    }

    public static void CGLIB$SET_STATIC_CALLBACKS(Callback[] var0) {
        CGLIB$STATIC_CALLBACKS = var0;
    }

    private static final void CGLIB$BIND_CALLBACKS(Object var0) {
        DoorImpl$$EnhancerByCGLIB$$850acfc8 var1 = (DoorImpl$$EnhancerByCGLIB$$850acfc8)var0;
        if (!var1.CGLIB$BOUND) {
            var1.CGLIB$BOUND = true;
            Object var10000 = CGLIB$THREAD_CALLBACKS.get();
            if (var10000 == null) {
                var10000 = CGLIB$STATIC_CALLBACKS;
                if (var10000 == null) {
                    return;
                }
            }

            var1.CGLIB$CALLBACK_0 = (MethodInterceptor)((Callback[])var10000)[0];
        }

    }

    public Object newInstance(Callback[] var1) {
        CGLIB$SET_THREAD_CALLBACKS(var1);
        DoorImpl$$EnhancerByCGLIB$$850acfc8 var10000 = new DoorImpl$$EnhancerByCGLIB$$850acfc8();
        CGLIB$SET_THREAD_CALLBACKS((Callback[])null);
        return var10000;
    }

    public Object newInstance(Callback var1) {
        CGLIB$SET_THREAD_CALLBACKS(new Callback[]{var1});
        DoorImpl$$EnhancerByCGLIB$$850acfc8 var10000 = new DoorImpl$$EnhancerByCGLIB$$850acfc8();
        CGLIB$SET_THREAD_CALLBACKS((Callback[])null);
        return var10000;
    }

    public Object newInstance(Class[] var1, Object[] var2, Callback[] var3) {
        CGLIB$SET_THREAD_CALLBACKS(var3);
        DoorImpl$$EnhancerByCGLIB$$850acfc8 var10000 = new DoorImpl$$EnhancerByCGLIB$$850acfc8;
        switch(var1.length) {
        case 0:
            var10000.<init>();
            CGLIB$SET_THREAD_CALLBACKS((Callback[])null);
            return var10000;
        default:
            throw new IllegalArgumentException("Constructor not found");
        }
    }

    public Callback getCallback(int var1) {
        CGLIB$BIND_CALLBACKS(this);
        MethodInterceptor var10000;
        switch(var1) {
        case 0:
            var10000 = this.CGLIB$CALLBACK_0;
            break;
        default:
            var10000 = null;
        }

        return var10000;
    }

    public void setCallback(int var1, Callback var2) {
        switch(var1) {
        case 0:
            this.CGLIB$CALLBACK_0 = (MethodInterceptor)var2;
        default:
        }
    }

    public Callback[] getCallbacks() {
        CGLIB$BIND_CALLBACKS(this);
        return new Callback[]{this.CGLIB$CALLBACK_0};
    }

    public void setCallbacks(Callback[] var1) {
        this.CGLIB$CALLBACK_0 = (MethodInterceptor)var1[0];
    }

    static {
        CGLIB$STATICHOOK1();
    }
}

这个类继承自我们的实现类DoorImpl,然后里面包含一个和父类方法签名一致的open方法,这就是重写了父类方法。
我们找到open方法然后看调用逻辑:

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

如果定义并设置了MethodInterceptor的实现类,就调用MethodInterceptor.intercept方法,如果没有呢,就直接调用父类open方法。此人是我们再考虑Spring中的@Transactional注解使用cglib动态生成字节码技术进行动态代理的话,依旧是会有上面提到的同一个service中A方法调用B方法问题,因为其本质还是调用了实现类中的open方法,而不是代理类中open方法。

cglib动态代理和jdk动态代理使用场景

  1. jdk动态代理要求必须实现接口,若某个需要代理的类未实现任何接口,就需要使用cglib
  2. cglib通过继承机制进行动态代理,如果某个需要代理的类是final修饰或者需要代理的方法是final修饰的,cglib无法进行动态代理
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值