写点什么

JDK 动态代理的实现机制

用户头像
xzy
关注
发布于: 2021 年 03 月 14 日
JDK动态代理的实现机制

前言


Spring AOP 使用了两种代理机制:一种是基于 JDK 的动态代理;另一种是基于 CGLib 的动态代理。为了彻底搞懂 Spring AOP 的实现原理,我阅读了这两种代理模式的源码,本篇文章将通过一个非常简单的案例来分析 JDK 动态代理的实现。


案例


创建一个计算器类进行加法操作,在操作前打印日志

工程环境


JDK: 1.8

maven 3.6.1

IDEA: 2020.3.2


编写被代理类


因为 jdk 只提供针对接口的代理,首先需要创建 Calculator 接口,由于这里只是简单地测试,不再引入复杂的业务逻辑,Calculator 接口里只有一个 add 方法,对传入的两个 int 参数进行加法计算,并返回 int 类型的和。


// package com.xzy.jdkpublic interface Calculator {    int add(int i, int j);}
复制代码


MyCalculator 作为实现类,实现了 add 方法。

// package com.xzy.jdkpublic class MyCalculator implements Calculator {    @Override    public int add(int i, int j) {        int result = i + j;        return result;    }}
复制代码


编写创建代理的类


Calculate 的静态方法 getProxy,根据传入的需要被代理的 Calculator 实例,创建其代理对象,在执行 add 方法前,打印日志。

// package com.xzy.jdkpublic class CalculatorProxy {    public static Calculator getProxy(Calculator calculator) {        ClassLoader loader = calculator.getClass().getClassLoader();        Class<?>[] interfaces = calculator.getClass().getInterfaces();                InvocationHandler h = (proxy, method, args) -> {            Object result = null;            if ("add".equals(method.getName())) {                System.out.println("执行add方法");            }            result = method.invoke(calculator, args);            return result;        };        Object proxy = Proxy.newProxyInstance(loader, interfaces, h);        return (Calculator) proxy;    }
复制代码

编写测试类


将系统属性 sun.misc.ProxyGenerator.saveGeneratedFiles 设置为 true,会将生成的动态代理类保存到本地。


// com.xzy.jdk.Testpublic class Test {    public static void main(String[] args) {        //将生成的代理类保存到本地        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");                Calculator proxy = CalculatorProxy.getProxy(new MyCalculator());        int result = proxy.add(1, 1);        System.out.println("result = " + result);        System.out.println(proxy.getClass());    }}
复制代码


运行结果



执行add方法result = 2com.sun.proxy.$Proxy0
复制代码


原理分析


在运行测试类时,由于我们设置了 sun.misc.ProxyGenerator.saveGeneratedFiles 属性,因此会将 JDK 动态代理生成的实现类 $Proxy0,保存到本地。


注:考虑篇幅问题,这里仅展示 add 方法。

// package com.sun.proxy;public final class $Proxy0 extends Proxy implements Calculator {    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 int add(int var1, int var2) throws { try { return (Integer)super.h.invoke(this, m3, new Object[]{var1, var2}); } catch (RuntimeException | Error var4) { throw var4; } catch (Throwable var5) { throw new UndeclaredThrowableException(var5); } }
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.xzy.jdk.Calculator").getMethod("add", Integer.TYPE, Integer.TYPE); m0 = Class.forName("java.lang.Object").getMethod("hashCode"); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); } }}
复制代码

在运行结果中打印了代理类的类名称 com.sun.proxy.$Proxy0,因此测试类 Test 中的 proxy 为生成的代理类 $Proxy0 的实例。


下面我们继续看一下生成的代理类 $Proxy0 的源码,这个代理类中有四个 Method 类型的类变量,在最下面的静态代码块中,通过反射的方式对这四个 Method 类型的类变量分别进行了初始化,他们分别对应 Object 的 equals、toString、hashCode 以及 Calculator 的 add 方法。

$Proxy0 类中只有一个构造函数,需传入一个类型为 java.lang.reflect.InvocationHandler 的参数,在实例化

$Proxy0 时,传入的实例化参数其实是在 CalculatorProxy 的 getProxy 方法中创建的 InvocationHandler 实例。


在代理 $Proxy0 的 add 方法中将执行 InvocationHandler 的 invoke 方法,执行该方法时,将代理实例、代表 Calculator 的 add 方法的 Method 实例,以及参数传递过去,在 invoke 方法中,打印日志后通过反射执行被代理实例的 add 方法。执行的过程如下图:


可以通过代码来验证这个过程,修改 MyCalculator 的 add 方法,增加一行代码来打印方法执行的堆栈。

 public int add(int i, int j) {        new Exception().printStackTrace();        int result = i + j;        return result;    }
复制代码

执行测试类,结果如下:

执行add方法result = 2java.lang.Exception	at com.xzy.jdk.MyCalculator.add(MyCalculator.java:6)	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)	at java.base/java.lang.reflect.Method.invoke(Method.java:566)	at com.xzy.jdk.CalculatorProxy.lambda$getProxy$0(CalculatorProxy.java:17)	at com.sun.proxy.$Proxy0.add(Unknown Source)	at com.xzy.jdk.Test.main(Test.java:9)
复制代码

在打印的结果中,第 9 行是执行 CalculatorProxy 中创建的 InvocationHandler 的方法。由打印的堆栈可以验证我们上面推断的执行过程的正确性。


源码执行流程


  1. 调用 Proxy.newProxyInstance(loader, interfaces, h)方法。

  2. 调用 Proxy 的静态方法,会触发 Proxy 这个类的初始化,在 Proxy 初始化过程中,最重要的操作是对静态变量 proxyClassCache 进行初始化,代码附在下面,在构造函数中传入了两个非常重要的参数 KeyFactory 和 ProxyClassFactory,一定要记住这两个非常有用的参数。

  3. proxyClassCache 负载缓存生成代理类实例,从该 cache 中获取代理 Class 对象。

  4. 如果 cache 中没有,创建 java.lang.reflect.WeakCache.Factory 对象,通过该对象来创建代理 Class。

  5. java.lang.reflect.WeakCache.Factory 调用 proxyClassCache 中的 ProxyClassFactory 对象进行代理 Class 创建。

  6. 此时代理类 $Proxy0 已经存在 JVM 中,把 CalculatorProxy 中的 InvocationHandler 实例作为参数,调用 $Proxy0 的构造函数 $Proxy0(InvocationHandler var1) 创建代理对象。


private static final WeakCache<ClassLoader, Class<?>[], Class<?>>        proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
复制代码


总结


JDK 动态代理只能对接口进行代理,对被代理类的方法通过反射的方式调用。而下篇文章将会分析 cglib 的原理,cglib 可以对类进行代理,cglib 引入了 FastClass 机制,可以直接调用被代理类对象的方法,性能比 JDK 的动态代理高。


源码地址:https://github.com/xuziyang/proxy-demo


用户头像

xzy

关注

还未添加个人签名 2017.10.17 加入

还未添加个人简介

评论

发布
暂无评论
JDK动态代理的实现机制