【源码解析】JDK 动态代理实现

基本流程

主方法

在该方法中,我们呈现了在日常使用 JDK 动态代理机制的方法。

public class VehicleDynamicProxy {  
    /**  
     * 被代理对象  
     */  
    public Vehicle targetVehicle;  
  
    public VehicleDynamicProxy(Vehicle targetVehicle) {  
        this.targetVehicle = targetVehicle;  
    }  
  
    public Vehicle getProxy() {  
        //获取类的加载器  
        ClassLoader classLoader = targetVehicle.getClass().getClassLoader();  
        //获取类的接口数组  
        Class<?>[] interfaces = targetVehicle.getClass().getInterfaces();  
        //调用函数,用来的调用方法  
        InvocationHandler invocationHandler = new InvocationHandler() {  
            @Override  
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  
                Object result = null;  
                try {  
                    System.out.println("前置通知 before");  
                    //调用被代理对象的方法  
                    result = method.invoke(targetVehicle, args);  
                    System.out.println("返回通知 afterReturning");  
                } catch (Exception e) {  
                    //异常通知 AfterThrowing                    throw new RuntimeException(e);  
                } finally {  
                    //最终通知 After                }  
                return result;  
            }  
        };  
        //此时的proxy对象是实现了Vehicle接口,继承了Proxy类的代理对象  
        Vehicle proxy = (Vehicle) Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);  
        return proxy;  
    }  
}

解读:

  • 我们应该特别注意到 Proxy.newProxyInstance() 方法,该方法就是我们关注的生成代理对象的方法;
  • 接下来,我们进入到该方法的内部去观察;

Proxy.newProxyInstance ()

为突出方法重点,我们只保留了核心逻辑,省略了异常捕获和其他校验的代码。

public static Object newProxyInstance(ClassLoader loader,  
                                      Class<?>[] interfaces,  
                                      InvocationHandler h)  
    throws IllegalArgumentException  
{  
    Objects.requireNonNull(h);  
  
    final Class<?>[] intfs = interfaces.clone();  
    final SecurityManager sm = System.getSecurityManager();  
    if (sm != null) {  
        checkProxyAccess(Reflection.getCallerClass(), loader, intfs);  
    }  
	Class<?> cl = getProxyClass0(loader, intfs);  
  
    final Constructor<?> cons = cl.getConstructor(constructorParams);  
    
    return cons.newInstance(new Object[]{h});  
}

解读:

  • 我们先校验了相关权限,然后通过 getProxyClass0(loader, intfs); 生成代理类的 Class 对象(此时,我们可以通过这个对象生成代理类实例),最后我们通过构造器的方式生成代理类实例;
  • 毫无疑问,getProxyClass0 方法是最重要的。这也是我们探索的主要阵地;

getProxyClass0 ()

private static Class<?> getProxyClass0(ClassLoader loader,  
                                       Class<?>... interfaces) {  
    if (interfaces.length > 65535) {  
        throw new IllegalArgumentException("interface limit exceeded");  
    }  
  
    // If the proxy class defined by the given loader implementing  
    // the given interfaces exists, this will simply return the cached copy;    // otherwise, it will create the proxy class via the ProxyClassFactory    
    return proxyClassCache.get(loader, interfaces);  
}

解读:

  • 从源码注释我们就可以知道,倘若类加载器和接口数组都已经存在,那么就会通过 ProxyClassFactory 创建一个代理类的 Class 对象;
  • 很明显,我们应该看 ProxyClassFactory 类;

ProxyClassFactory

该类是 Proxy 类的一个静态内部工厂类。如下图所示:

我们还可以发现该类只有一个方法apply(),所以只有这个方法才可以生成我们的代理类:

接下来是对于ProxyClassFactory.apply()的解读,同样,我们会精简出核心的逻辑:

@Override  
    public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {  
  
        Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);  
        // package to define proxy class in
        String proxyPkg = null;       
        int accessFlags = Modifier.PUBLIC | Modifier.FINAL;  
        if (proxyPkg == null) {  
            // if no non-public proxy interfaces, use com.sun.proxy package  
            proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";  
        }  
  
	    long num = nextUniqueNumber.getAndIncrement();  
        String proxyName = proxyPkg + proxyClassNamePrefix + num;  
  
        /*  
         * Generate the specified proxy class.         */         
         */
	    byte[] proxyClassFile = ProxyGenerator.generateProxyClass(  
            proxyName, interfaces, accessFlags);  
       
        return defineClass0(loader, proxyName,  
                                proxyClassFile, 0, proxyClassFile.length);  
       
    }  
}

解读:

  • 首先我们先生成一个代理类的名字 proxyNameproxyClassNamePrefix 的值就为我们喜闻乐见的:

    同时也为我们指示了包名:

  • 然后,我们通过 generateProxyClass 方法生成了代理类的字节码,可以看到这里特别点名要使用到我们提供的接口数组

  • 最后,调用本地方法 defineClass0 生成 Class 对象:

  • 显然,我们应该继续追 generateProxyClass 方法的源码;

ProxyGenerator. generateProxyClass ()

参数描述:

  • var0:代理类的名字;
  • var1:接口数组;
  • var2:权限校验参数(不用管);
public static byte[] generateProxyClass(final String var0, Class<?>[] var1, int var2) {  
    ProxyGenerator var3 = new ProxyGenerator(var0, var1, var2);  
    final byte[] var4 = var3.generateClassFile();  
    if (saveGeneratedFiles) {  
        AccessController.doPrivileged(new PrivilegedAction<Void>() {  
            public Void run() {  
                try {  
                    int var1 = var0.lastIndexOf(46);  
                    Path var2;  
                    if (var1 > 0) {  
                        Path var3 = Paths.get(var0.substring(0, var1).replace('.', File.separatorChar));  
                        Files.createDirectories(var3);  
                        var2 = var3.resolve(var0.substring(var1 + 1, var0.length()) + ".class");  
                    } else {  
                        var2 = Paths.get(var0 + ".class");  
                    }  
  
                    Files.write(var2, var4, new OpenOption[0]);  
                    return null;                } catch (IOException var4x) {  
                    throw new InternalError("I/O exception saving generated file: " + var4x);  
                }  
            }  
        });  
    }  
  
    return var4;  
}

解读:

  • 其实该方法只用看前两行。通过 var3.generateClassFile() 生成一个 byte 数组 var4,该数组就是代理类对应的 Class 对象的字节表示,该数组会通过本地方法 defineClass0 生成 Class 对象;
  • saveGeneratedFiles 属性主要是决定程序是否将生成的动态代理对象保存到磁盘上,这个属性将在我们剖析生成的动态代理类结构发挥极大的作用;

总结

通过上述对于源码的解析,我们可以发现,动态代理对象的创建涉及到底层本地方法,也就是说,动态代理对象是通过我们提供的类信息由 JVM 虚拟机自动创建的。这就是动态代理区别于其他代理方式的根本不同。
动态代理方式提供了更加的灵活的选择。


不过,我们还没有解决为什么在生成代理对象的时候要给出接口的问题。
我们将通过解析运行阶段生成的代理对象来分析该问题。

代理对象详解

调试理解

我们先通过调试来看看这个对象究竟是什么:

解读:

  • 我们的动态代理类原来叫做 $Proxy0,我们接下来直接通过一些手段拿到该动态代理类的字节码,然后进行反编译;

拿到动态代理对象

注意到,ProxyGenerator.generateProxyClass() 方式中的 saveGeneratedFiles 参数,我们通过这个参数来让动态代理对象写入到磁盘中。
有两种方法将该参数调整为 true。
设计 vm 参数:

-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true

在生成代理对象前调用如下代码:

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

于是,我们就可以在项目主目录下,拿到 JVM 自动生成的动态代理类:

代理对象剖析

反编译后如下,我们在该类中省略了无关的方法实现,例如 hashcodetoString,只保留了被代理对象实现的方法:

package com.sun.proxy;

import com.yelanyanyu.dynamicProxy.Vehicle;
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 Vehicle {
    private static Method m3;
    private static Method m4;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final void run() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final String fly(int var1) throws  {
        try {
            return (String)super.h.invoke(this, m4, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    static {
        try {
            m3 = Class.forName("com.yelanyanyu.dynamicProxy.Vehicle").getMethod("run");
            m4 = Class.forName("com.yelanyanyu.dynamicProxy.Vehicle").getMethod("fly", Integer.TYPE);
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

解读:

  • 从方法的描述 $Proxy0 extends Proxy implements Vehicle 可以看出,该代理类的类名就是我们之前分析的 $Proxy0
  • 该类继承原来的 Proxy 类,实现我们提供的接口;
  • 特别注意return (String)super.h.invoke(this, m4, new Object[]{var1});,我们可以再次印证我在前一篇文章中得出的结论,代理对象的方法调用是通过转发到 InvocationHandler 的 invoke 来实现的;

为什么一定是接口

现在我们就可以来解释解释这个问题了。原因主要有:

  • 代理对象是动态创建的,也就是说,程序员刚开始并不知道其类名,也不能在编译阶段使用这个代理对象。所以,我们只能使用 OOP 原则中的向下转型,用 $Proxy0 的父类或者其实现的接口来接收,但是 $Proxy0 的父类已经是 Proxy 类了(Proxy 类中没有对应的方法);由于 Java 是单继承的,所以只能用实现接口的方式,来让接口接受 JVM 创建的动态代理对象,如下:
Vehicle proxy = (Vehicle) Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);

解读:

  • 我们不可能写成 $Proxy0 proxy = Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);

  • 或者 Proxy proxy = (Proxy) Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);

  • 另一个原因则是默认了对象采用了合适的模板设计模式:类的绝大部分方法都在接口中有实现,代理对象可以使用到被代理对象的绝大部分方法。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Math210

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值