核心思想:代理模式 (Proxy Pattern)
在解释动态和静态代理之前,我们先理解什么是“代理”。
代理模式就是为一个对象提供一个“替身”或“占位符”,来控制对这个原始对象的访问。你可以把代理想象成一个中介或经纪人。你不会直接和明星(目标对象)联系,而是通过他的经纪人(代理对象)。经纪人可以在明星工作前后做很多事:过滤不重要的请求、安排日程、谈合同、做安保等。
1. 静态代理 (Static Proxy)
核心定义
静态代理是指代理类是在编译时就已经创建好了,它的 .java
源文件和 .class
文件是真实存在的。代理类和被代理类(目标类)通常会实现同一个接口。
简单来说,就是你(开发者)需要手动为每一个需要代理的类,编写一个对应的代理类。
特点
- 一对一:一个代理类只服务于一个具体的目标类(或一类接口)。如果你有
UserService
和OrderService
两个接口需要代理,你就得手动创建UserServiceProxy
和OrderServiceProxy
两个代理类。 - 代码量大:如果需要代理的类很多,会导致项目中出现大量的代理类,产生很多重复的模板代码。
- 不灵活:如果接口发生变化(比如增加一个方法),那么目标类和代理类都需要同步修改,维护性差。
示例
假设我们有一个卖票的接口:
// 1. 共同的接口
interface TicketService {
void sellTicket();
}
// 2. 目标对象 (火车站)
class TrainStation implements TicketService {
@Override
public void sellTicket() {
System.out.println("【火车站】: 出售一张火车票。");
}
}
// 3. 静态代理类 (黄牛/代售点) - 你需要手动编写这个类
class TicketProxy implements TicketService {
private TrainStation target; // 持有目标对象的引用
public TicketProxy(TrainStation target) {
this.target = target;
}
@Override
public void sellTicket() {
// 增强逻辑 (前置)
System.out.println("【代理】: 收取服务费 5 元。");
// 调用目标对象的核心方法
target.sellTicket();
// 增强逻辑 (后置)
System.out.println("【代理】: 提供售后咨询服务。");
}
}
// 使用
public class Client {
public static void main(String[] args) {
TrainStation station = new TrainStation();
TicketProxy proxy = new TicketProxy(station);
proxy.sellTicket(); // 调用代理的方法
}
}
输出:
【代理】: 收取服务费 5 元。
【火车站】: 出售一张火车票。
【代理】: 提供售后咨询服务。
2. 动态代理 (Dynamic Proxy)
核心定义
动态代理是指代理类是在程序运行时,通过反射或字节码技术动态生成的,它的 .java
文件并不存在于你的项目源码中。你不需要手动编写任何代理类。
你只需要编写一个通用的处理器 (Handler),这个处理器包含了你的增强逻辑。然后,在运行时,框架会根据你的目标对象,动态地创建一个遵守相同接口规则的代理对象。
特点
- 一对多:一个通用的处理器 (Handler) 可以为实现了任意接口的任意多个目标对象服务,极大地提高了代码的复用性。
- 代码量小:你只需要维护那个通用的处理器逻辑,而不需要为每个业务类都写一个代理类。
- 非常灵活:接口的增删改对代理逻辑几乎没有影响,因为代理类是动态生成的,它总能匹配最新的接口。
- 框架的基石:Spring AOP、RPC 框架(如 Dubbo)的远程调用等都是基于动态代理实现的。
示例 (以 JDK 动态代理为例)
我们还是用卖票的例子,但这次用动态代理实现:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// 1. 共同的接口 (和静态代理一样)
interface TicketService {
void sellTicket();
void inquire(); // 假设接口新增一个方法
}
// 2. 目标对象 (和静态代理一样, 但实现了新方法)
class TrainStation implements TicketService {
@Override
public void sellTicket() {
System.out.println("【火车站】: 出售一张火车票。");
}
@Override
public void inquire() {
System.out.println("【火车站】: 查询余票。");
}
}
// 3. 动态代理的处理器 (Handler) - 这是核心!
class DynamicProxyHandler implements InvocationHandler {
private Object target; // 任何目标对象都可以
public DynamicProxyHandler(Object target) {
this.target = target;
}
/**
* @param proxy 动态生成的代理对象本身 (很少使用)
* @param method 被调用的目标方法
* @param args 调用目标方法时传入的参数
*/
@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;
}
}
// 使用
public class Client {
public static void main(String[] args) {
// 创建目标对象
TrainStation station = new TrainStation();
// 创建处理器
InvocationHandler handler = new DynamicProxyHandler(station);
// 使用 Proxy 类的静态方法,动态地创建一个代理对象
TicketService proxyInstance = (TicketService) Proxy.newProxyInstance(
station.getClass().getClassLoader(), // 目标类的类加载器
station.getClass().getInterfaces(), // 目标类实现的所有接口
handler // 我们写的处理器
);
// 调用代理对象的方法
System.out.println("--- 调用 sellTicket ---");
proxyInstance.sellTicket();
System.out.println("\n--- 调用 inquire ---");
proxyInstance.inquire();
// 你可以看看这个代理到底是什么类型
System.out.println("\n代理对象的类型: " + proxyInstance.getClass().getName());
}
}
输出:
--- 调用 sellTicket ---
【动态代理】: 方法 [sellTicket] 执行前,进行日志记录...
【火车站】: 出售一张火车票。
【动态代理】: 方法 [sellTicket] 执行后,处理完成。
--- 调用 inquire ---
【动态代理】: 方法 [inquire] 执行前,进行日志记录...
【火车站】: 查询余票。
【动态代理】: 方法 [inquire] 执行后,处理完成。
代理对象的类型: com.sun.proxy.$Proxy0
你会发现,inquire
这个新增的方法也被自动代理了,我们没有修改任何代理逻辑。代理对象的类型是一个运行时生成的神秘名字 $Proxy0
。
总结:静态代理 vs 动态代理
特性 | 静态代理 (Static Proxy) | 动态代理 (Dynamic Proxy) |
---|---|---|
创建时机 | 编译时 (代理类 .java 文件已存在) | 运行时 (代理类在内存中动态生成) |
代码量 | 大,一个代理类对应一个目标类 | 小,一个 Handler 可服务于多个目标类 |
灵活性 | 低,接口变更需要同步修改代理类 | 高,接口变更无需修改代理逻辑 |
耦合度 | 代理类与目标类紧耦合 | 代理逻辑与目标类松耦合 |
适用场景 | 代理逻辑简单且固定,代理类数量少的场景 | 框架开发,如 Spring AOP、RPC 等需要对大量类进行通用增强的场景 |
总结:
- 静态代理是“体力活”,你得为每个想代理的类亲手写一个代理。
- 动态代理是“脑力活”,你只需写一个通用的处理规则,程序会在运行时自动帮你生成所有代理。