深入 JDK 动态代理实现

1,398 阅读4分钟

作为每个人都知道,但又很少能说完全清楚的技术:动态代理。

今天,我们就彻底搞明白他。

深入 JDK 动态代理实现

基本使用

没有花里胡哨的,就下面的代码:

UserService userService = new UserServiceImpl();
UserService o = (UserService) Proxy.newProxyInstance(UserService.class.getClassLoader(), new Class[]{UserService.class}, new InvocationHandler() {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("--before--");
        Object invoke = method.invoke(userService, args);
        System.out.println("--after--");
        return invoke;
    }
});

可以看出,关键在于代理的创建:Proxy.newProxyInstance。后面我们也以这里为入口深入。

关键步骤解析

生成代理类

生成代理类是动态代理中的最复杂的步骤,而真正生成代理类字节码的方法位于:java.lang.reflect.Proxy.ProxyClassFactory#apply

精简一下方法,主要有以下几个步骤:

    //前置校验
    interfaceClass = Class.forName(intf.getName(), false, loader);
    if(xxx){
        throw new IllegalArgumentException(xxx);
    }
    ...
    //数据准备
    int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
    ...
    String proxyName = proxyPkg + proxyClassNamePrefix + num;
    ...
    //生成代理类 bytes
    byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
        proxyName, interfaces, accessFlags);
    ...
    //加载
    return defineClass0(loader, proxyName,
        proxyClassFile, 0, proxyClassFile.length);

生成代理类与加载是动态代理的核心。

组装 ProxyMethod

Step 1: Assemble ProxyMethod objects for all methods to generate proxy dispatching code for.

这里只是将收集到的方法元信息封装为sun.misc.ProxyGenerator.ProxyMethod 类存储,并没有真正的生成 code。

这里还特地也保存了了hashCode,equals,toString方法。

addProxyMethod(hashCodeMethod, Object.class);
addProxyMethod(equalsMethod, Object.class);
addProxyMethod(toStringMethod, Object.class);
        
...

sigmethods.add(new ProxyMethod(name, parameterTypes, returnType,
    exceptionTypes, fromClass));

组装 FieldInfo / MethodInfo

Step 2: Assemble FieldInfo and MethodInfo structs for all of fields and methods in the class we are generating.

添加构造方法,并根据上一步组装好的ProxyMethod添加成员属性。

在这一步中,会调用sun.misc.ProxyGenerator.ProxyMethod#generateMethod方法,为其生成code。

//生成构造方法
this.methods.add(this.generateConstructor());
...
    //生成成员属性
    fields.add(new FieldInfo(pm.methodFieldName,
        "Ljava/lang/reflect/Method;",
         ACC_PRIVATE | ACC_STATIC));
    //添加方法
    methods.add(pm.generateMethod());
...
//添加静态初始化代码块
methods.add(generateStaticInitializer());
     

构造最终类

Step 3: Write the final class file.

根据上面几步收集到的信息,生成最终 byte 数组。

                // u4 magic;
dout.writeInt(0xCAFEBABE);
                            // u2 minor_version;
dout.writeShort(CLASSFILE_MINOR_VERSION);
                            // u2 major_version;
dout.writeShort(CLASSFILE_MAJOR_VERSION);

cp.write(dout); // (write constant pool)

                            // u2 access_flags;
dout.writeShort(accessFlags);
                            // u2 this_class;
dout.writeShort(cp.getClass(dotToSlash(className)));
                            // u2 super_class;
dout.writeShort(cp.getClass(superclassName));
...

最终结果

通过配置 sun.misc.ProxyGenerator.saveGeneratedFiles=true 可以将生成的代理类字节码保存下来。

删除了我们无需关心 hashCode,toString等方法后,反编译结果如下:

package com.sun.proxy;

import git.frank.proxy.UserService;
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 UserService {
    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 String getUserInfo() throws  {
        try {
            return (String)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");
            m3 = Class.forName("git.frank.proxy.UserService").getMethod("getUserInfo");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

加载代理类

我们以 openJdk 中的代码为参考,这里实际调用的方法是:JavaLangAccess#defineClass

Defines a class with the given name to a class loader.

他真正的代码位于 java.lang.System 中,以匿名内部类的形式。

    // Allow privileged classes outside of java.lang
    private static void setJavaLangAccess() {
     SharedSecrets.setJavaLangAccess(new JavaLangAccess() {
            ...
            public Class<?> defineClass(ClassLoader loader, String name, byte[] b, ProtectionDomain pd, String source) {
                return ClassLoader.defineClass1(loader, name, b, 0, b.length, pd, source);
            }
            ...
    }

所以,这里就是使用给定的类加载器去加载刚才生成的class。当被代理方法getUserInfo被调用时,便会被代理类转至传入的 InvocationHandler#invoke 方法中。

那么,为什么在代码中没有直接调用 ClassLoader.defineClass1,而是这样绕了一圈呢?

我们再仔细看看,因为 ClassLoader 中的 defineClass 相关代码都是 protected 的呀~,根本没办法直接调用。

所以 openJdk 才创造了一个 JavaLangAccess 类,专门用于让外界调用 java.lang 包内的内容。

构造对象

构造对象就简单很多,单纯的使用反射调用构造方法去创建刚才生成的代理类实例。

使用入参为 InvocationHandler 的构造方法,并传入用户编写的 InvocationHandler 对象。

final Constructor<?> cons = cl.getConstructor(constructorParams);
...
cons.newInstance(new Object[]{h})

这里构造的实际上就是上面生成的 com.sun.proxy.$Proxy0 类实例。

动态代理中的缓存机制

使用动态代理时并不是每次都要经过上面那一大串代码的,只要同 classLoader + interfaces 相同,都可以使用同一个 proxy 类。

所以 jdk 为动态代理生成的 class 也提供了缓存机制,下次使用的时候,直接通过反射创建 proxy 实例就可以,无需再重新生成 clazz 。

这里的缓存主要通过 java.lang.reflect.WeakCache 实现。

使用 key + sub-key 唯一确定一个 value。

其中,key 为传入的 classLoader,sub-key 为通过 key + 传入的接口 class 计算得出。

缓存构成如下图:

image.png

懒加载

起初 valuesMap 中缓存的并不是最中的代理类,而是一个 ProxyClassFactory

当调用 valueFactory.apply(key, parameter) 生成真正的代理类后,会将缓存由 factory 对象更换为真正的代理。

if (!valuesMap.replace(subKey, this, cacheValue)) {
    throw new AssertionError("Should not reach here");
}

完整的获取缓存流程如下:

weak-cache.png