Java动态代理机制详解
动态代理
一、从现实生活开始:一个完美的比喻
想象一个场景:你想邀请一个明星(我们叫他 “周杰伦”)来你的生日派对唱歌。
- 你遇到的问题:
- 你没有周杰伦的直接联系方式。
- 即使有,他本人也很忙,不会直接处理这种预约请求(谈合同、安排时间、讨论曲目等)。
- 你不想,或者无法直接和他沟通这些琐事。
- 解决方案: 你该怎么办呢?你会找到他的经纪人。
- 经纪人做了什么:
- 你直接和经纪人沟通。
- 经纪人代表周杰伦,和你谈合同、费用、曲目等事宜。
- 对于你提出的要求(比如“唱《晴天》”),经纪人可能会做一些额外的工作: 事前:记录你的需求,可能还会帮你看看预算够不够(增强你的请求)。 事后:告诉你没问题,并且安排档期(处理结果)。
- 最后,在派对上,经纪人确保周杰伦出场并完成了唱歌这个核心动作。
在这个比喻中:
- 周杰伦 = 真实对象:他有核心能力(唱歌),但不直接对外服务。
- 经纪人 = 代理对象:他代表真实对象,对外提供服务,并且可以在执行核心动作前后做一些额外的事情。
- 你 = 客户端:你只想达成目的(听到歌),不关心是直接还是间接通过谁完成的。
二、Java 代理的核心思想
Java 代理就是编程世界里的“经纪人”。它的核心思想是:为一个对象(真实对象)提供一个代理(代理对象),以控制对这个对象的访问,并且可以在执行真实对象的方法前后加入自定义的逻辑。
为什么要这么做?
- 功能增强:在不修改原始类代码的前提下,为方法添加日志、性能统计、事务管理、权限校验等“额外功能”。这符合著名的 AOP(面向切面编程) 思想。
- 控制访问:延迟加载(比如大型图片、文件,直到真正需要时才创建真实对象)、权限控制(检查是否有权调用某方法)。
- 简化客户端调用:客户端可能只需要一个统一的接口,而不用关心背后真实对象的复杂实现或生命周期。
三、Java 中两种主要的代理模式
Java 主要提供了两种方式来创建代理:“静态代理”和“动态代理”。动态代理又分为基于 JDK 的和基于第三方库(如 CGLIB)的。
1. 静态代理
特点:代理类在程序运行前就已经手动编写、编译好了。.java 文件是实实在在存在的。
如何实现:
- 定义一个接口(规定明星能做什么,比如
Singer接口,里面有sing()方法)。 - 一个真实对象(周杰伦
JayChou类)实现这个接口。 - 一个代理类(经纪人
SingerAgent类)也实现这个接口。 - 代理类内部持有一个真实对象的引用(经纪人认识周杰伦)。
- 代理类实现接口方法时,调用真实对象的方法,并在其前后添加额外逻辑。
代码示例:
// 1. 接口
public interface Singer {
void sing();
}
// 2. 真实对象
public class JayChou implements Singer {
@Override
public void sing() {
System.out.println("周杰伦演唱《晴天》");
}
}
// 3. 代理类
public class SingerAgent implements Singer { // 代理也要实现相同接口
private Singer singer; // 持有真实对象的引用
public SingerAgent(Singer singer) {
this.singer = singer;
}
@Override
public void sing() {
System.out.println("经纪人:签订合同,安排档期..."); // 前置增强
singer.sing(); // 调用真实对象的核心方法
System.out.println("经纪人:结算费用,清理现场..."); // 后置增强
}
}
// 4. 客户端使用
public class Client {
public static void main(String[] args) {
Singer jay = new JayChou(); // 创建真实对象
Singer agent = new SingerAgent(jay); // 创建代理对象,传入真实对象
agent.sing(); // 客户端调用的是代理对象的方法!
}
}输出:
经纪人:签订合同,安排档期...
周杰伦演唱《晴天》
经纪人:结算费用,清理现场...缺点:非常不灵活。如果接口增加了新方法,真实对象和代理对象都需要修改。一个代理类只能代理一种类型的对象。
2. 动态代理
特点:代理类是在程序运行时,通过反射机制动态生成的。我们不需要手动编写代理类的 .java 源文件。大大提高了灵活性。
Java 自身提供了基于接口的动态代理机制,主要使用 java.lang.reflect.Proxy 和 java.lang.reflect.InvocationHandler。
如何实现:
- 同样需要一个接口和一个真实对象。
- 定义一个
InvocationHandler实现类(调用处理器)。你可以把它理解为“代理行为的模板”或“经纪人的工作手册”。它只有一个invoke方法,所有对代理对象的方法调用都会转到这里。 - 使用
Proxy.newProxyInstance()方法,动态地在内存中创建一个代理实例。
代码示例:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// 1. 接口和真实对象(和上面静态代理一样)
public interface Singer { void sing(); }
public class JayChou implements Singer {
@Override
public void sing() { System.out.println("周杰伦演唱《晴天》"); }
}
// 2. 实现 InvocationHandler
public class MyInvocationHandler implements InvocationHandler {
private Object target; // 目标对象/真实对象,这里用 Object,更通用
public MyInvocationHandler(Object target) {
this.target = target;
}
// 所有对代理对象的方法调用,都会转发到这个方法来处理
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("经纪人:签订合同,安排档期...");
// 通过反射,调用真实对象的方法
Object result = method.invoke(target, args);
System.out.println("经纪人:结算费用,清理现场...");
return result;
}
}
// 3. 客户端使用
public class Client {
public static void main(String[] args) {
// 创建真实对象
Singer jay = new JayChou();
// 创建 InvocationHandler,传入真实对象
InvocationHandler handler = new MyInvocationHandler(jay);
// 动态创建代理对象!
Singer proxy = (Singer) Proxy.newProxyInstance(
jay.getClass().getClassLoader(), // 使用真实对象的类加载器
jay.getClass().getInterfaces(), // 代理对象需要实现的接口
handler); // 调用处理器
// 调用代理对象的方法
proxy.sing();
}
}输出(和静态代理一样):
经纪人:签订合同,安排档期...
周杰伦演唱《晴天》
经纪人:结算费用,清理现场...巨大优势:
- 非常灵活:这个
MyInvocationHandler是通用的!它可以代理任何实现了接口的对象(Singer,Actor,Player...),成为一个“万能经纪人”。如果要代理新类型,只需创建新的真实对象和接口,无需编写新的代理类。 - 解耦:将“代理行为” (
InvocationHandler) 和“被代理对象”完全分离开。
JDK 动态代理的限制:它只能代理实现了接口的类。如果一个类没有实现任何接口,JDK 的 Proxy 就无能为力了。
解决方案:CGLIB 动态代理
- 第三方库 CGLIB 可以解决这个限制。它通过继承的方式动态创建子类来作为代理。
- 它会重写父类(被代理类)的所有方法,并在子类中织入增强逻辑。
- 因为是通过继承,所以无法代理 final 类或 final 方法。
- Spring AOP 等框架默认会优先使用 JDK 动态代理,如果目标对象没有接口,则会自动切换到 CGLIB。
总结
| 特性 | 静态代理 | 动态代理 (JDK) |
|---|---|---|
| 实现方式 | 手动编写、编译代理类 | 运行时通过反射动态生成代理类 |
| 灵活性 | 低。一个代理类代理一种类型 | 高。一个 InvocationHandler 可代理多种类型 |
| 性能 | 略好(直接编译) | 略差(反射调用),但现代 JVM 对其优化很好 |
| 要求 | 需要接口 | 必须有接口 |
| 应用场景 | 早期简单应用,或明确知道代理类数量的情况 | 几乎所有现代框架(Spring AOP, RPC, 测试框架等) |