### 目录
- [@TOC(目录)](#目录 @TOC 前言 1. 比喻理解 2. 代码理解 1. 签名 2. 参数1:ClassLoader loader (类加载器) 3. 参数2:
Class<?>[] interfaces(接口数组) 4. 参数3:InvocationHandler h (调用处理器) 3. 测试与运行 4. 代理字节码原理 总结) - [前言](#目录 @TOC 前言 1. 比喻理解 2. 代码理解 1. 签名 2. 参数1:ClassLoader loader (类加载器) 3. 参数2:
Class<?>[] interfaces(接口数组) 4. 参数3:InvocationHandler h (调用处理器) 3. 测试与运行 4. 代理字节码原理 总结) - [1. 比喻理解](#目录 @TOC 前言 1. 比喻理解 2. 代码理解 1. 签名 2. 参数1:ClassLoader loader (类加载器) 3. 参数2:
Class<?>[] interfaces(接口数组) 4. 参数3:InvocationHandler h (调用处理器) 3. 测试与运行 4. 代理字节码原理 总结) - [2. 代码理解](#目录 @TOC 前言 1. 比喻理解 2. 代码理解 1. 签名 2. 参数1:ClassLoader loader (类加载器) 3. 参数2:
Class<?>[] interfaces(接口数组) 4. 参数3:InvocationHandler h (调用处理器) 3. 测试与运行 4. 代理字节码原理 总结) - [1. 签名](#目录 @TOC 前言 1. 比喻理解 2. 代码理解 1. 签名 2. 参数1:ClassLoader loader (类加载器) 3. 参数2:
Class<?>[] interfaces(接口数组) 4. 参数3:InvocationHandler h (调用处理器) 3. 测试与运行 4. 代理字节码原理 总结) - [2. 参数1:ClassLoader loader (类加载器)](#目录 @TOC 前言 1. 比喻理解 2. 代码理解 1. 签名 2. 参数1:ClassLoader loader (类加载器) 3. 参数2:
Class<?>[] interfaces(接口数组) 4. 参数3:InvocationHandler h (调用处理器) 3. 测试与运行 4. 代理字节码原理 总结) - [3. 参数2:`Class<?>\[\] interfaces` (接口数组)](#目录 @TOC 前言 1. 比喻理解 2. 代码理解 1. 签名 2. 参数1:ClassLoader loader (类加载器) 3. 参数2:
Class<?>[] interfaces(接口数组) 4. 参数3:InvocationHandler h (调用处理器) 3. 测试与运行 4. 代理字节码原理 总结) - [4. 参数3:InvocationHandler h (调用处理器)](#目录 @TOC 前言 1. 比喻理解 2. 代码理解 1. 签名 2. 参数1:ClassLoader loader (类加载器) 3. 参数2:
Class<?>[] interfaces(接口数组) 4. 参数3:InvocationHandler h (调用处理器) 3. 测试与运行 4. 代理字节码原理 总结) - [3. 测试与运行](#目录 @TOC 前言 1. 比喻理解 2. 代码理解 1. 签名 2. 参数1:ClassLoader loader (类加载器) 3. 参数2:
Class<?>[] interfaces(接口数组) 4. 参数3:InvocationHandler h (调用处理器) 3. 测试与运行 4. 代理字节码原理 总结) - [4. 代理字节码原理](#目录 @TOC 前言 1. 比喻理解 2. 代码理解 1. 签名 2. 参数1:ClassLoader loader (类加载器) 3. 参数2:
Class<?>[] interfaces(接口数组) 4. 参数3:InvocationHandler h (调用处理器) 3. 测试与运行 4. 代理字节码原理 总结) - [总结](#目录 @TOC 前言 1. 比喻理解 2. 代码理解 1. 签名 2. 参数1:ClassLoader loader (类加载器) 3. 参数2:
Class<?>[] interfaces(接口数组) 4. 参数3:InvocationHandler h (调用处理器) 3. 测试与运行 4. 代理字节码原理 总结)
前言
通过一个通俗易懂的比喻,快速理解 Java 动态代理的核心设计思想。
通过手写 JDK 动态代理的完整代码,详细剖析其核心参数与运行机制,并深入 JVM 内存中的代理类字节码,揭示动态代理的底层实现原理。
1. 比喻理解
例子:
- 大明星(小美):十分擅长唱歌和跳舞。打算举办演唱会,举办演唱会的过程非常繁琐。小美只想把核心精力放在唱歌和跳舞上。
- 代理(小帅):专门负责处理小美唱歌和跳舞之外的所有繁杂事务。
- 赞助商 :希望小美来唱歌时,不能直接和小美对话,必须先和经纪人小帅交涉。
- 调用过程:小帅和赞助商谈妥后(前置准备),把小美带到舞台上,小美只管唱歌(核心业务)。唱完歌后,小帅还要负责结算收尾工作(后置收尾)。
在这个过程中,小帅 就是小美 的代理人,也就是我们代码中常说的代理对象。
2. 代码理解
我们首先写一个接口,叫做 Star(明星),声明两个方法:sing 和 dance。
java
public interface Star {
String sing(String songName);
void dance();
}
小美是大明星,实现 Star 接口,擅长 sing 与 dance:
java
public class BigStar implements Star {
@Override
public String sing(String songName) {
System.out.println("小美正在唱" + songName);
return "谢谢大家";
}
@Override
public void dance() {
System.out.println("小美正在跳舞");
}
}
接下来,我们编写用于创建代理对象的工具类 ProxyUtil。
java
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ProxyUtil {
public static Star createProxy(Star star) {
return (Star) Proxy.newProxyInstance(
ProxyUtil.class.getClassLoader(),//参数1
new Class<?>[]{Star.class},//参数2
new InvocationHandler() {//参数3
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("sing".equals(method.getName()) || "dance".equals(method.getName())) {
System.out.println("找演唱会地点");
Object result = method.invoke(star, args);
System.out.println("收尾");
return result;
}
return method.invoke(star, args);
}
}
);
}
}
为了让代理对象正常工作,Proxy.newProxyInstance 必须明确以下三个参数:
1. 签名
java
public static Object newProxyInstance(
ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h
)
2. 参数1:ClassLoader loader (类加载器)
在程序运行过程中,动态代理会在内存中实时生成代理类的字节码。为了让 JVM 识别并加载该字节码,需要提供一个类加载器。通常使用当前工具类的类加载器 ProxyUtil.class.getClassLoader() 或被代理类的类加载器。
3. 参数2:Class<?>[] interfaces (接口数组)
此参数表示代理类要实现哪些接口。
在我们的例子中,代理要代替明星唱歌跳舞,因此需要实现 Star 接口,写为 new Class<?>[]{Star.class}。
4. 参数3:InvocationHandler h (调用处理器)
当外部调用代理对象的任何方法时,都会转发到此处理器的 invoke 方法中执行。
3. 测试与运行
我们编写 Main 类进行测试:
java
public class Main {
public static void main(String[] args) {
// 1. 创建真实对象
BigStar bigStar = new BigStar();
// 2. 创建代理对象
Star proxyStar = ProxyUtil.createProxy(bigStar);
// 3. 调用代理方法
String ret = proxyStar.sing("Lucky");
System.out.println(ret);
}
}
运行后,控制台输出如下:
text
找演唱会地点
小美正在唱Lucky
收尾
谢谢大家
4. 代理字节码原理
看看代理对象的伪代码:
java
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 Star {
private static Method m1; // 代表 Object.equals()
private static Method m2; // 代表 Object.toString()
private static Method m3; // 代表 Star.sing()
private static Method m4; // 代表 Star.dance()
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Object.class);
m2 = Class.forName("java.lang.Object").getMethod("toString");
m3 = Class.forName("Star").getMethod("sing", String.class);
m4 = Class.forName("Star").getMethod("dance");
} catch (Exception e) {
throw new NoSuchMethodError(e.getMessage());
}
}
public $Proxy0(InvocationHandler h) {
super(h); // 将处理器传递给父类 java.lang.reflect.Proxy
}
@Override
public final String sing(String songName) {
try {
// 所有方法调用都会转发到超类持有的 InvocationHandler 的 invoke 方法中
return (String) super.h.invoke(this, m3, new Object[]{songName});
} catch (Error | RuntimeException e) {
throw e;
} catch (Throwable t) {
throw new UndeclaredThrowableException(t);
}
}
@Override
public final void dance() {
try {
super.h.invoke(this, m4, null);
} catch (Error | RuntimeException e) {
throw e;
} catch (Throwable t) {
throw new UndeclaredThrowableException(t);
}
}
}
动态生成的代理类命名为 $Proxy0,它继承了 java.lang.reflect.Proxy 并且实现了目标接口 Star。Java 不支持多重继承,$Proxy0 已经占用了继承 Proxy 的名额。所以只能代理接口,无法代理实例化的类.
$Proxy0 的构造方法接收一个 InvocationHandler h,并通过 super(h) 传给父类 Proxy 统一保管。
在重写的接口方法(如 sing、dance)中,没有包含任何业务逻辑,而是直接调用 super.h.invoke(this, method, args).
总结
- 动态代理的核心思想是解耦,通过代理对象将繁琐的非核心业务与真实对象的核心业务隔离开来。
- JDK 动态代理利用
java.lang.reflect.Proxy类的newProxyInstance方法,在运行期动态地在内存中生成实现了指定接口的代理类字节码。 - 代理类(如
$Proxy0)继承自Proxy并实现目标接口,其内部持有一个InvocationHandler对象,所有接口方法的调用都会被重定向并转发到该处理器的invoke方法中执行。