一个类实现了和目标类相同的接口,持有目标对象,方法调用时委托给目标,并可在前后加额外逻辑
| 要素 | 含义 | 为什么需要 |
|---|---|---|
| ① 实现相同接口 | 代理类和目标类都 implement 同一个 Interface | 调用方不关心是代理还是目标——对调用方透明 |
| ② 持有目标引用 | 代理内部保存一个目标对象的引用(构造器传入或运行时指定) | 代理自己不干活,最终要委托给真正的干活人 |
| ③ 委托 + 增强 | 代理方法里:先做额外的事 → 调 target.xxx()→ 再做额外的事 |
不修改目标代码的前提下增加功能 |
如果把代理模式的本质用伪代码表达,就是:
// 代理类的"灵魂"就在这一个方法里
public void doWork() {
// ===== 前置增强:代理的附加逻辑 =====
before();
// ===== 委托:真正干活的是目标对象 =====
target.doWork();
// ===== 后置增强:代理的附加逻辑 =====
after();
}
静态代理和动态代理的区别,仅仅在于这个代理类是怎么来的:
StaticProxy.java,编译时就有了.java文件品牌方不知道也不需要知道明星在干嘛(代理对调用方透明),但最终还是要明星亲自去拍广告、唱歌、演戏(真正的活还得目标对象干)。经纪人(代理)帮明星过滤邀约、谈价格、排档期——不修改明星的行为,但增加了额外功能。
// ========== 1. 定义接口 ==========
interface Service {
void doWork();
}
// ========== 2. 目标类(真正干活的) ==========
class RealService implements Service {
public void doWork() {
System.out.println("RealService: 干活");
}
}
// ========== 3. 代理类(增强 + 委托) ==========
class StaticProxy implements Service {
private Service target; // 持有目标对象的引用
public StaticProxy(Service target) {
this.target = target; // 通过构造器注入目标
}
public void doWork() {
System.out.println("前置:日志"); // 前置增强
target.doWork(); // 委托给目标
System.out.println("后置:事务"); // 后置增强
}
}
// ========== 4. 使用 ==========
public class Main {
public static void main(String[] args) {
Service real = new RealService(); // 创建目标对象
Service proxy = new StaticProxy(real); // 用代理包装目标
proxy.doWork(); // 调用代理,代理增强 + 委托
}
}
输出:
前置:日志
RealService: 干活
后置:事务
| 特点 | 说明 |
|---|---|
| 代理类在编译前就存在 | StaticProxy.java是实实在在的 .java文件,编译成 .class,运行时直接用 |
| 一对一绑定接口 | 一个 StaticProxy 只能代理实现了 Service接口的类,换一个接口就要新写一个代理类 |
Java 动态代理的核心是 java.lang.reflect.Proxy和 InvocationHandler:
| API | 作用 |
|---|---|
Proxy.newProxyInstance(...) |
在运行时动态生成代理类的字节码,并返回代理实例 |
InvocationHandler.invoke(...) |
代理实例的每个方法调用都会转发到这个 invoke 方法中 |
newProxyInstance()需要三个参数:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// ========== 接口和目标类同上 ==========
interface Service {
void doWork();
}
class RealService implements Service {
public void doWork() {
System.out.println("RealService: 干活");
}
}
// ========== 使用动态代理 ==========
public class DynamicProxyDemo {
public static void main(String[] args) {
// 1. 创建目标对象
Service real = new RealService();
// 2. 创建动态代理实例
Service proxy = (Service) Proxy.newProxyInstance(
real.getClass().getClassLoader(), // 参数1:类加载器
real.getClass().getInterfaces(), // 参数2:目标实现的接口
new InvocationHandler() { // 参数3:调用处理器
@Override
public Object invoke(
Object proxy, // 代理实例自身
Method method, // 被调用的方法
Object[] args // 方法参数
) throws Throwable {
System.out.println("前置:日志"); // 前置增强
// 反射调用目标对象的真正方法
Object result = method.invoke(real, args); // 委托
System.out.println("后置:事务"); // 后置增强
return result;
}
}
);
// 3. 调用代理方法
proxy.doWork();
}
}
输出与静态代理完全一致:
前置:日志
RealService: 干活
后置:事务
proxy.doWork()被调用InvocationHandler.invoke()method.invoke(target, args)→ 后置增强InvocationHandler可以同时代理多个接口,所有接口的所有方法调用都走同一个 invoke()方法。
| 参数 | 类型 | 含义 | 何时用 |
|---|---|---|---|
| proxy | Object |
代理实例自身(即 Proxy.newProxyInstance返回的那个对象) |
一般不直接用,只用来获取代理类信息 |
| method | java.lang.reflect.Method |
当前被调用的方法(如 Service.doWork) |
用来反射调用目标对象:method.invoke(target, args) |
| args | Object[] |
方法调用时传入的参数 | 透传给目标方法 |
proxy参数的方法(如 proxy.toString()),这会导致无限递归——因为调用代理方法又会触发 invoke,invoke 里又调代理方法,死循环直到栈溢出。
一个自然的疑问:既然代理对象的每个方法都只是转发给 handler.invoke(),那为什么不直接调用 handler.invoke()呢?为什么还需要 Proxy.newProxyInstance()生成的那个代理对象?
答案是:Proxy 对象不是可有可无的中间人,它承担着不可替代的关键角色。
| 作用 | 说明 |
|---|---|
| 类型适配器 | 把通用的 InvocationHandler包装成具体的业务接口(如 Calculator、GithubService),让调用方以自然、类型安全的方式使用 |
| 方法转发器 | 代理对象上的每个接口方法,实现体都只有一行:handler.invoke(...)。它自己不干活,只是把调用转发给背后的 InvocationHandler |
| 隐藏反射细节 | 调用者无需手动获取 Method对象、包装参数、处理反射异常,只需像调用普通对象一样写 proxy.add(3, 5) |
| 保证接口契约 | 代理对象实现了你指定的接口,因此可以通过编译器的类型检查,也能在 IDE 中获得自动补全 |
| 多接口支持 | 一个代理对象可以同时实现多个接口,而 InvocationHandler只是一个普通对象,不具备多接口类型 |
用代码对比来感受差异:
// ===== 没有 Proxy:调用方必须手动处理反射 =====
InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("前置:日志");
Object result = method.invoke(real, args);
System.out.println("后置:事务");
return result;
}
};
// 调用方每次都要手动反射,痛苦!
Method method = Calculator.class.getMethod("add", int.class, int.class);
Object result = handler.invoke(null, method, new Object[]{3, 5});
// 没有类型安全,编译器不检查,IDE 不补全,异常要 try-catch
// ===== 有 Proxy:调用方像调用普通对象一样自然 =====
Calculator proxy = (Calculator) Proxy.newProxyInstance(
real.getClass().getClassLoader(),
real.getClass().getInterfaces(),
handler
);
int result = proxy.add(3, 5); // 类型安全!IDE 自动补全!编译器检查!
// 调用者完全不知道背后有 InvocationHandler 在干活
InvocationHandler这个通用处理器能够以特定接口的形态被调用,从而兼顾了灵活性与类型安全。InvocationHandler是"策略",代理对象是"外壳"——两者配合:外壳保证接口契约,策略提供具体实现。
| 维度 | 静态代理 | 动态代理(JDK) |
|---|---|---|
| 代理类何时生成 | 编译前(开发者手写 .java) | 运行时(JVM 动态生成字节码) |
| 是否有 .java 文件 | 有 | 没有 |
| 代理类数量 | N 个接口 → N 个代理类 | 1 个 InvocationHandler → N 个接口 |
| 灵活性 | 低,接口变了代理类也要改 | 高,handler 是通用的 |
| 性能 | 高,直接方法调用 | 稍低,有反射开销 (但现代 JVM 已大幅优化反射性能) |
| 是否必须基于接口 | 不必须(可继承类) | 必须基于接口 (JDK 动态代理的限制) |
| 可代理的接口数 | 任意(手动写) | 可同时代理多个接口 |
| 实现相同接口 | 持有目标引用 | 委托 + 增强 |
无论静态还是动态,代理的核心思想都是一样的:代理站在调用方和目标之间,既能让调用方无感使用(接口相同),又能拦截每个方法调用(委托 + 增强)。
| 场景 | 说明 | 例子 |
|---|---|---|
| 日志记录 | 方法调用前后自动打日志 | log("开始调用: " + method.getName()) |
| 事务管理 | 方法前开事务,方法后提交/回滚 | Spring 的 @Transactional就是通过代理实现的 |
| 权限校验 | 方法调用前检查是否有权限 | if (!hasPermission) throw new SecurityException() |
| 性能监控 | 统计方法执行时间 | long start = System.nanoTime(); ... long cost = ... |
| 远程调用(RPC) | 代理封装网络通信细节 | Retrofit 的接口代理,调用方法时实际发网络请求 |
| 延迟加载 | 第一次访问时才真正初始化目标对象 | Hibernate 的懒加载代理 |
代理类来源:手写的 .java文件 → 编译成 .class→ 直接使用
代理粒度:一个代理类绑定一个接口
代码量:N 个接口 = N 个代理类 = 大量重复代码
适用:接口少、逻辑简单、追求极致性能
代理类来源:JVM 运行时直接生成字节码 → 没有 .java文件
代理粒度:一个 InvocationHandler 可代理多个接口
代码量:一个 handler 通吃所有接口
适用:接口多、通用增强、AOP 场景