JDK源码分析——动态代理源码(Proxy类)
引言
为了方便后续回顾,特此记录 JDK 动态代理的核心源码分析。动态代理最重要的实现入口是 Proxy.newInstance,我们将直接从该方法入手进行分析。
newProxyInstance 方法分析
newProxyInstance 是创建动态代理实例的核心静态方法。该方法主要包含三个参数:
- ClassLoader loader:类加载器。一般情况下传入当前的 ClassLoader,但在某些特定场景(如上一节模拟实现)中可能传入 URLClassLoader。
- Class<?>[] interfaces:代理类需要实现的接口数组。JDK 封装较好,要求传入接口数组。
- 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;
}总结
通过源码分析可以看到,最关键的生成二进制码的方法 generateProxyClass 是 native 的。虽然未能直接看到底层的字节码生成细节,但前期的处理逻辑非常有学习价值,例如:
- 缓存机制:JDK 如何处理重复创建问题,如何利用
ClassLoader作为一级缓存键。 - 并发控制:如何使用
pendingGenerationMarker和wait/notify处理并发生成请求。 - 命名策略:如何通过计数器避免类名重复。
- 包可见性:如何处理非 public 接口的包名继承问题。
说明:本文分析的源码片段中包含部分原始类型(Raw Types,如Class cl、Map cache未泛型化),这可能基于较早版本的 JDK 源码或为了简化展示。现代 JDK 版本(如 JDK 8+)中已广泛使用泛型。核心逻辑(缓存、native 生成)在后续版本中保持一致。
版权声明:本文为原创文章,版权归 戴老师的博客 所有,转载请联系博主获得授权。
本文地址:https://1diff.fun/archives/jdk-yuan-ma-fen-xi--dong-tai-dai-li-yuan-ma-proxy-lei.html
如果对本文有什么问题或疑问都可以在评论区留言,我看到后会尽量解答。