查看原文
其他

从头捋了一遍 JDK 动态代理,此次之后,永生难忘!

Java精选 2022-08-09

>>号外:关注“Java精选”公众号,回复“面试资料”,免费领取资料!Java精选面试题”小程序,3000+ 道面试题在线刷,最新、最全 Java 面试题!

动态代理,这个词在Java的世界里面经常被提起,尤其是对于部分(这里强调“部分”二字,因为有做了一两年就成大神的,实力强的令人发指,这类人无疑是非常懂动态代理这点小伎俩的)做了一两年新人来说,总是摸不清楚来龙去脉,一两年是个坎,为什么是一两年,才入门的新人可能对这东西没什么感觉,没到这一步,做了很久开发的人显然是明白这其中原理的,而做了一两年的,知其然而不知其所以然,所以一两年工作经验的人很多是很茫然的。
那么,这里就相对比较比较深入一点的介绍JDK动态代理的原理。这样子介绍完,明白了其中的道理,我相信你会永远记得JDK动态代理的思想。顺带一句,cglib做的事儿和JDK动态代理做的事儿从结局上来说差不多,方式不太一样。
1、先从JDK的源代码说起,动态代理这部分源码,Oracle版本和OpenJDK的源码是不太一样的,貌似Oracle版本最核心那点东西没开源,F3进去我反正是找不到,我也懒得去找,但是原理都是一致的,这里就挑选OpenJDK的。
我们回顾一下JDK动态代理,先说宏观原理,相信都懂,使用JDK动态代理最常见,至少对于我来说就是Spring的AOP部分,并且是AOP部分的声明式事务部分。
a、定义一个接口Car:
public interface Car {
    void drive(String driverName, String carName);
}
b、定义接口Car的一个实现类Audi:
public class Audi implements Car {

    @Override
    public void drive(String driverName, String carName) {
        System.err.println("Audi is driving... " + "driverName: " + driverName + ", carName" + carName);
    }
}
c、定义一个动态调用的控制器CarHandler:
public class CarHandler implements InvocationHandler {
    
    private Car car;
    
    public CarHandler(Car car) {
        this.car = car;
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.err.println("before");
        method.invoke(car, args);
        System.err.println("after");
        return null;
    }

}
d、测试类ProxyTest:
public class ProxyTest {
    
    @Test
    public void proxyTest() throws Exception {
        Car audi = (Car) Proxy.newProxyInstance(Car.class.getClassLoader(), new Class<?>[] {Car.class}, new CarHandler(new Audi()));
        audi.drive("name1""audi");
    }
}
e、输出结果:
before
Audi is driving... driverName: name1, carNameaudi
after
上面这段,相信大家都懂,也明白原理,这就是所谓的知其然,但是不一定都能知其所以然。接下来就解释下“知其所以然”。
进入到Proxy类的newProxyInstance方法:
public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    
{
        if (h == null) {
            throw new NullPointerException();
        }

        /*
         * Look up or generate the designated proxy class.
         */

        Class<?> cl = getProxyClass(loader, interfaces);

        /*
         * Invoke its constructor with the designated invocation handler.
         */

        try {
            Constructor cons = cl.getConstructor(constructorParams);
            return cons.newInstance(new Object[] { h });
        } catch (NoSuchMethodException e) {
            throw new InternalError(e.toString());
        } catch (IllegalAccessException e) {
            throw new InternalError(e.toString());
        } catch (InstantiationException e) {
            throw new InternalError(e.toString());
        } catch (InvocationTargetException e) {
            throw new InternalError(e.toString());
        }
    }
关键的3行:
// 创建代理类
Class<?> cl = getProxyClass(loader, interfaces);
// 实例化代理对象
Constructor cons = cl.getConstructor(constructorParams);
返回的是代理类的实例化对象。接下来的调用就很清晰了。
那么,JDK动态代理最核心的关键就是这个方法:
Class<?> cl = getProxyClass(loader, interfaces);
进入该方法,这个方法很长,前面很多都是铺垫,在方法的最后调用了一个方法:
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                    proxyName, interfaces);
这个方法就是产生代理对象的方法。我们先不看前后,只关注这一个方法,我们自己来写一个该方法:
public class ProxyTest {
    
    @SuppressWarnings("resource")
    @Test
    public void proxyTest() throws Exception {
        byte[] bs = ProxyGenerator.generateProxyClass("AudiImpl"new Class<?>[] {Car.class});
        new FileOutputStream(new File("d:/AudiImpl.class")).write(bs);
    }
}
于是,我们就在D盘里面看到了一个叫做AudiImpl.class的文件,对该文件进行反编译,得到下面这个类:
public final class AudiImpl extends Proxy implements Car {

    private static final long serialVersionUID = 5351158173626517207L;

    private static Method m1;
    private static Method m3;
    private static Method m0;
    private static Method m2;

    public AudiImpl(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 void drive(String paramString1, String paramString2) {
        try {
            this.h.invoke(this, m3, new Object[] { paramString1, paramString2 });
            return;
        } 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 String toString() {
        try {
            return (String) this.h.invoke(this, m2, null);
        } 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") });
            m3 = Class.forName("com.mook.core.service.Car").getMethod("drive",
                    new Class[] { Class.forName("java.lang.String"), Class.forName("java.lang.String") });
            m0 = Class.forName("java.lang.Object").getMethod("hashCode"new Class[0]);
            m2 = Class.forName("java.lang.Object").getMethod("toString"new Class[0]);
        } catch (NoSuchMethodException localNoSuchMethodException) {
            throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
        } catch (ClassNotFoundException localClassNotFoundException) {
            throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
        }
    }
}
这个时候,JDK动态代理所有的秘密都暴露在了你的面前,当我们调用drive方法的时候,实际上是把方法名称传给控制器,然后执行控制器逻辑。这就实现了动态代理。
Spring AOP有两种方式实现动态代理,如果基于接口编程,默认就是JDK动态代理,否则就是cglib方式,另外spring的配置文件里面也可以设置使用cglib来做动态代理,关于二者的性能问题,网上也是众说纷纭,不过我个人的观点,性能都不是问题,不太需要去纠结这一点性能问题。
事实上,如果你明白这一点,当你去阅读mybatis源码的时候是很有帮助的,mybatis的接口方式做方法查询就充分利用了这里的JDK动态代理。否则如果不明白原理,看mybatis的源码的接口方式是很费劲的,当然了,这只是mybatis利用动态代理的冰山一角,要完全看懂mybaits源码还有其他的许多难点,比如mybatis是以xml文件来配置sql语句的。

作者:神一样的存在

cnblogs.com/dreamroute/p/5273888.html
往期精选  点击标题可跳转

源码分析 Mybatis 事务管理,将颠覆你心中目前对事务的理解!

Java 中如何优雅的根治 null 值引起的 Bug 问题,速看分析!

包装严重的 IT 圈,作为面试官,是如何甄别应聘者呢?

Spring Cloud 微服务架构,小团队到底适不适合,如何应用?

创业公司技术总监,去上市公司面试,结果“凉凉”了!

写了 10 年代码,做过的项目都下线了,程序员的意义在哪里?

半路出家的菜鸡程序员,北漂五年,给刚入行朋友的一些忠告,发自肺腑

新入职的 Java 同事天天净写垃圾代码,难道就没办法?

Spring 新版本摒弃 JVM,支持独立部署,难道要自立门户?

浅析 Netty 源码如何实现心跳机制与断线重连,还有不懂么?

点个赞,就知道你“在看”!

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存