核心定义
JDK 动态代理是 Java 官方提供的一种在运行时动态创建代理对象的技术。它允许我们创建一个“替身”对象,这个替身可以在不修改原始业务代码的情况下,拦截对原始对象方法的调用,并在调用前后插入自定义的逻辑(即 AOP 中的“通知”)。
工作原理
JDK 动态代理的核心是两个组件:java.lang.reflect.Proxy 类和 java.lang.reflect.InvocationHandler 接口。
它的工作流程可以分解为以下几个步骤:
1. 前提:定义接口和目标类
首先,你必须有一个接口,以及一个实现了该接口的目标类(也就是你想要代理的原始业务类)。
// 1. 定义接口
public interface SmsService {
String send(String message);
}
// 2. 目标类,实现接口
public class SmsServiceImpl implements SmsService {
@Override
public String send(String message) {
System.out.println("【核心业务】: 正在发送短信: " + message);
return "短信发送成功";
}
}
2. 编写处理器 (InvocationHandler)
你需要创建一个实现了 InvocationHandler 接口的类。这个类是代理的灵魂,所有对代理对象方法的调用最终都会被转发到它的 invoke 方法里。你所有的增强逻辑(比如日志、计时、权限检查)都写在这里。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
// 3. 编写处理器,这是所有增强逻辑的所在地
public class MyInvocationHandler implements InvocationHandler {
// 持有对目标对象的引用
private final Object target;
public MyInvocationHandler(Object target) {
this.target = target;
}
/**
* 当你调用代理对象的任何方法时,这个 invoke 方法就会被执行。
* @param proxy 动态生成的代理对象本身 (很少使用)
* @param method 被调用的目标方法 (例如 send 方法)
* @param args 调用目标方法时传入的参数
* @return 目标方法的返回值
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// --- 前置增强 ---
System.out.println("【前置通知】: 方法 " + method.getName() + " 即将执行...");
// 通过反射调用原始目标对象的方法
Object result = method.invoke(target, args);
// --- 后置增强 ---
System.out.println("【后置通知】: 方法 " + method.getName() + " 执行完毕。");
return result; // 返回原始方法的执行结果
}
}
3. 生成并使用代理对象
最后,使用 Proxy.newProxyInstance() 这个静态方法来动态地创建代理对象。
public class JdkProxyFactory {
public static Object getProxy(Object target) {
return Proxy.newProxyInstance(
// 1. 目标对象的类加载器 (ClassLoader)
// 用于加载动态生成的代理类。
target.getClass().getClassLoader(),
// 2. 目标对象实现的所有接口 (Class<?>[])
// 代理对象会实现这些接口,告诉代理它需要有哪些方法。
target.getClass().getInterfaces(),
// 3. 我们编写的处理器 (InvocationHandler)
// 将方法调用分派到这里。
new MyInvocationHandler(target)
);
}
}
// --- 客户端使用 ---
public class Main {
public static void main(String[] args) {
// 创建目标对象
SmsService target = new SmsServiceImpl();
// 获取代理对象
SmsService proxy = (SmsService) JdkProxyFactory.getProxy(target);
// 通过代理对象调用方法
String response = proxy.send("Hello, JDK Proxy!");
System.out.println("客户端收到响应: " + response);
// 代理对象的真实类型是什么?
System.out.println("代理对象的类型: " + proxy.getClass().getName());
}
}
运行结果:
【前置通知】: 方法 send 即将执行...
【核心业务】: 正在发送短信: Hello, JDK Proxy!
【后置通知】: 方法 send 执行完毕。
客户端收到响应: 短信发送成功
代理对象的类型: com.sun.proxy.$Proxy0
从结果可以看出,我们调用 proxy.send() 时,MyInvocationHandler 中的前后置逻辑被成功执行了。并且代理对象的类型是一个由 JVM 在运行时动态生成的、名字类似 $Proxy0 的类。
限制与缺点
JDK 动态代理虽然强大,但它有一个非常重要的核心限制:
-
必须基于接口进行代理 (Most Important Limitation)
- 它只能代理实现了接口的类。 这是因为
Proxy.newProxyInstance()的第二个参数要求传入一个接口数组。生成的代理类会实现这些接口,但它不会继承你的目标类。 - 因此,如果一个类没有实现任何接口(一个普通的 POJO),JDK 动态代理就无法为它创建代理。这是它最大的局限性,也是为什么 Spring AOP 需要引入 CGLIB 作为补充的原因。
- 它只能代理实现了接口的类。 这是因为
-
性能开销
- 由于其底层是基于 Java 反射 (Reflection) 机制(即
method.invoke(...)),相比直接调用,会存在一定的性能开销。不过,在现代的 JVM 中,反射的性能已经得到了极大的优化,对于绝大多数业务场景来说,这点开销可以忽略不计。
- 由于其底层是基于 Java 反射 (Reflection) 机制(即
-
类型转换问题
- 代理对象
proxy的类型是com.sun.proxy.$Proxy0,它只实现了SmsService接口。你不能将它强制类型转换为SmsServiceImpl。 proxy instanceof SmsService会返回true。proxy instanceof SmsServiceImpl会返回false。- 这在某些需要具体实现类类型的场景下可能会导致
ClassCastException。
- 代理对象
总结
| 特性 | 描述 |
|---|---|
| 核心技术 | Java 反射 (java.lang.reflect.Proxy, InvocationHandler) |
| 代理方式 | 在运行时动态地实现目标对象的所有接口,生成代理类。 |
| 最大限制 | 目标对象必须实现接口。无法代理没有接口的普通类。 |
| 优点 | Java 原生支持,无需引入任何第三方库。 |
| 缺点 | 必须有接口;存在一定的反射性能开销;无法强转为具体实现类。 |

166

被折叠的 条评论
为什么被折叠?



