引言

为了方便后续回顾,特此记录 JDK 动态代理的核心源码分析。动态代理最重要的实现入口是 Proxy.newInstance,我们将直接从该方法入手进行分析。

newProxyInstance 方法分析

newProxyInstance 是创建动态代理实例的核心静态方法。该方法主要包含三个参数:

  1. ClassLoader loader:类加载器。一般情况下传入当前的 ClassLoader,但在某些特定场景(如上一节模拟实现)中可能传入 URLClassLoader。
  2. Class<?>[] interfaces:代理类需要实现的接口数组。JDK 封装较好,要求传入接口数组。
  3. InvocationHandler h:调用处理器,负责拦截方法调用。

除了第一个参数外,其他参数与自定义实现逻辑类似。以下是该方法的核心源码:

public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h)
    throws IllegalArgumentException
{
    // 如果 InvocationHandler 为空,抛出空指针异常
    if (h == null) {
        throw new NullPointerException();
    }

    /*
     * 查找或生成指定的代理类。
     * 这里最主要的地方在于调用 getProxyClass 方法获取自动生成的动态代理类的二进制码。
     * 在自定义实现中,我们通常是通过生成 Java 文件,动态编译成 class 文件,
     * 然后通过 URLClassLoader.loadClass 来获取对应的二进制码。
     */
    Class<?> cl = getProxyClass(loader, interfaces);

    /*
     * 通过反射获取构造器并实例化。
     * 获取对应 class 的 Constructor,然后通过这个 Constructor 来实例化。
     */
    try {
        Constructor<?> cons = cl.getConstructor(constructorParams);
        return (Object) 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());
    }
}

上述方法中,除了最重要的 getProxyClass 外,其他逻辑都比较容易理解。接下来重点分析 getProxyClass 方法。

getProxyClass 方法分析

getProxyClass 方法负责查找或创建代理类。其逻辑较为复杂,主要涉及参数校验、缓存机制以及类的生成。

参数校验与缓存键构建

首先,方法会对传入的接口数组进行校验,包括数量限制、可见性、是否为接口以及是否重复等。

public static Class<?> getProxyClass(ClassLoader loader, 
                                     Class<?>... interfaces)
    throws IllegalArgumentException
{
    // JDK 考虑周全,限制了接口数量不能超过 65535
    if (interfaces.length > 65535) {
        throw new IllegalArgumentException("interface limit exceeded");
    }

    Class<?> proxyClass = null; // 最终要生成的二进制码类对象

    // 存放对应的 Interface 的名字,用于构建缓存 key
    String[] interfaceNames = new String[interfaces.length];
    // 用于检测 interface 重复记录的 HashSet
    Set<Class<?>> interfaceSet = new HashSet<>();

    for (int i = 0; i < interfaces.length; i++) {
        /*
         * 验证类加载器是否将此接口名称解析为同一个 Class 对象。
         */
        String interfaceName = interfaces[i].getName();
        Class<?> interfaceClass = null;
        try {
            // 创建对应接口的二进制码,第二个参数 false 表示不需要初始化
            interfaceClass = Class.forName(interfaceName, false, loader);
        } catch (ClassNotFoundException e) {
            // 处理异常
        }
        // 如果创建出来的二进制码和原来的接口不一样,表示这个接口对于这个 classloader 来说是不可见的
        if (interfaceClass != interfaces[i]) {
            throw new IllegalArgumentException(
                interfaces[i] + " is not visible from class loader");
        }

        /*
         * 验证 Class 对象实际上是否代表一个接口。
         * 这里规定了必须通过接口来代理,即必须面向接口编程。
         */
        if (!interfaceClass.isInterface()) {
            throw new IllegalArgumentException(
                interfaceClass.getName() + " is not an interface");
        }

        /*
         * 验证接口是否重复。
         * 在循环 interfaces 的时候,把每个 interface 都添加到 interfaceSet 里。
         * 如果已经有了,则抛异常说明接口重复。
         */
        if (interfaceSet.contains(interfaceClass)) {
            throw new IllegalArgumentException(
                "repeated interface: " + interfaceClass.getName());
        }
        interfaceSet.add(interfaceClass);

        interfaceNames[i] = interfaceName; // 把每个接口名放到 interfaceNames 数组里
    }

    /*
     * 使用代理接口的字符串表示作为代理类缓存的键。
     * 这样做的好处是,使用类的字符串表示可以形成对类的隐式弱引用。
     */
    Object key = Arrays.asList(interfaceNames); // 把 Interface 数组转换成 list 作为 key

缓存查找与并发控制

JDK 使用了多级缓存机制来提高性能。loaderToCache 是一个 Map,key 是 ClassLoader,value 是对应的缓存 Map。

    /*
     * 查找或创建该类加载器的代理类缓存。
     * loaderToCache 也是一个 Map,key 是 classloader,value 是对应的缓存 HashMap。
     * 如果下次调用该方法创建代理并传入同一个 classloader,可以直接从 cache 里取,增加速度。
     */
    Map<Object, Object> cache;
    synchronized (loaderToCache) {
        cache = (Map<Object, Object>) loaderToCache.get(loader);
        if (cache == null) {
            cache = new HashMap<>();
            loaderToCache.put(loader, cache);
        }
    }

    /*
     * 使用 key 在代理类缓存中查找接口列表。
     * 结果可能有三种:
     * 1. null:当前类加载器中尚无该接口列表的代理类。
     * 2. pendingGenerationMarker:该接口列表的代理类正在生成中。
     * 3. WeakReference<Class>:该接口列表的代理类已生成。
     */
    synchronized (cache) {
        do {
            Object value = cache.get(key);
            if (value instanceof Reference) {
                proxyClass = (Class<?>) ((Reference<?>) value).get();
            }
            if (proxyClass != null) {
                // 代理类已生成,直接返回
                return proxyClass;
            } else if (value == pendingGenerationMarker) {
                // 代理类正在生成,等待完成
                try {
                    cache.wait();
                } catch (InterruptedException e) {
                    // 类生成耗时较短,可安全忽略中断
                }
                continue;
            } else {
                /*
                 * 尚未生成也未正在生成,标记为正在生成状态。
                 * pendingGenerationMarker 是一个静态常量 (new Object())。
                 */
                cache.put(key, pendingGenerationMarker);
                break;
            }
        } while (true);
    }

代理类生成与定义

如果缓存未命中,则进入真正的生成逻辑。这里涉及包名的确定以及最终的二进制码生成。

    try {
        String proxyPkg = null; // 代理类的包名

        /*
         * 记录非 public 代理接口的包,确保代理类定义在同一个包中。
         * 动态创建的代理,其修饰类型必须和接口的修饰类型一致。
         * 接口可以是 public 或默认修饰类型。如果是默认修饰类型,只能被同包下的类看到,
         * 因此必须为该代理类创建一个包名。如果是 public 则无所谓。
         */
        for (int i = 0; i < interfaces.length; i++) {
            int flags = interfaces[i].getModifiers();
            if (!Modifier.isPublic(flags)) {
                String name = interfaces[i].getName();
                int n = name.lastIndexOf('.');
                // 包括 ".",例如 "com.cjb.proxy."
                String pkg = ((n == -1) ? "" : name.substring(0, n + 1));

                if (proxyPkg == null) {
                    proxyPkg = pkg;
                } else if (!pkg.equals(proxyPkg)) {
                    throw new IllegalArgumentException(
                        "non-public interfaces from different packages");
                }
            }
        }

        if (proxyPkg == null) { 
            // 如果没有非 public 接口,使用默认包
            proxyPkg = "";  
        }

        {
            /*
             * 为要生成的代理类选择一个名称。
             * nextUniqueNumber 是一个计数器,初始值为 0,每调用一次自增。
             * 代理类名字格式为:$Proxy1, $Proxy2, $Proxy3... 以避免重复。
             */
            long num;
            synchronized (nextUniqueNumberLock) {
                num = nextUniqueNumber++;
            }
            String proxyName = proxyPkg + proxyClassNamePrefix + num;

            /*
             * 生成指定的代理类。
             * 下面两个方法是最后生成代理对象的关键。
             * 值得注意的是,generateProxyClass 是 native 修饰的,
             * 意味着最关键的地方不是用 Java 实现的。
             */
            byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, interfaces);
            try {
                proxyClass = defineClass0(loader, proxyName,
                    proxyClassFile, 0, proxyClassFile.length);
            } catch (ClassFormatError e) {
                throw new IllegalArgumentException(e.toString());
            }
        }
        // 添加到所有生成的代理类集合中,用于 isProxyClass 判断
        proxyClasses.put(proxyClass, null);

    } finally {
        /*
         * 清理缓存中的 "pending generation" 状态。
         * 如果成功生成,存入缓存(弱引用);否则移除保留条目。
         * 所有情况下,通知该缓存中保留条目的所有等待者。
         */
        synchronized (cache) {
            if (proxyClass != null) {
                cache.put(key, new WeakReference<>(proxyClass));
            } else {
                cache.remove(key);
            }
            cache.notifyAll();
        }
    }
    return proxyClass;
}

总结

通过源码分析可以看到,最关键的生成二进制码的方法 generateProxyClassnative 的。虽然未能直接看到底层的字节码生成细节,但前期的处理逻辑非常有学习价值,例如:

  • 缓存机制:JDK 如何处理重复创建问题,如何利用 ClassLoader 作为一级缓存键。
  • 并发控制:如何使用 pendingGenerationMarkerwait/notify 处理并发生成请求。
  • 命名策略:如何通过计数器避免类名重复。
  • 包可见性:如何处理非 public 接口的包名继承问题。
说明:本文分析的源码片段中包含部分原始类型(Raw Types,如 Class clMap cache 未泛型化),这可能基于较早版本的 JDK 源码或为了简化展示。现代 JDK 版本(如 JDK 8+)中已广泛使用泛型。核心逻辑(缓存、native 生成)在后续版本中保持一致。