这是 2020 年 5 月整理的设计模式专题第七篇。代理模式是面试高频中的高频,尤其是 JDK 动态代理的底层原理——Spring AOP 就是建立在它之上。本文从概念讲到底层字节码,层层深入。
简单说明
代理模式(Proxy Pattern):为其他对象提供一种代理,以控制对这个对象的访问。
- 代理对象在客户端和目标对象之间起到中介的作用;
- 代理模式能将代理对象和真实被调用的目标对象分离,一定程度上降低了系统的耦合度;
- 扩展性好,可以保护和增强目标对象;
- 但是会造成系统设计中类数目的增加,而且在客户端和目标对象之间加入了代理对象,会造成请求速度变慢,且增加系统的复杂度。
客户端 ——> 代理对象 ——> 真实目标对象
(中介、保护、增强)
静态代理
代理类在编译期就确定,一个代理类对应一个目标类(或接口):
/**
* 描述:目标接口
*/
public interface UserService {
void printName();
}
/**
* 描述:真实目标对象
*/
public class UserServiceImpl implements UserService {
@Override
public void printName() {
System.out.println("真实业务:打印姓名");
}
}
/**
* 描述:静态代理:和目标实现同一个接口,持有真实对象
*/
public class UserServiceProxy implements UserService {
private UserService target;
public UserServiceProxy(UserService target) {
this.target = target;
}
@Override
public void printName() {
System.out.println("代理增强:方法执行前记录日志");
target.printName();
System.out.println("代理增强:方法执行后统计耗时");
}
}
静态代理的缺点:一个代理类只能代理一种接口,如果系统有很多接口需要增强,就要写很多代理类——代理类爆炸。动态代理应运而生。
JDK 动态代理
JDK 动态代理在运行期为接口动态生成代理类,无需手写。
/**
* 描述:InvocationHandler:所有代理方法的调用都汇聚到这里
*/
public class UserServiceHandler implements InvocationHandler {
private Object target;
public UserServiceHandler(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;
}
}
// 使用:Proxy.newProxyInstance 动态创建代理对象
UserService proxy = (UserService) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new UserServiceHandler(target));
proxy.printName();
前提条件:被代理的类必须实现了接口(JDK 动态代理基于接口)。没有接口的实现类需要用 CGLIB(基于继承)。
导出 JDK 生成的动态代理类
# JVM 参数指令,生成 JDK 创建的动态代理类
-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true
或者在代码中加入:
System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
注意:生成的代理文件在项目根目录的
com.xxx.proxy下(对应被代理接口的包路径)。
生成的代理类长什么样
JDK 动态代理生成的代理类(反编译后)大致如下:
/**
* 描述:JDK 生成的 $Proxy0 代理类
*/
public final class $Proxy0 extends Proxy implements UserService {
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m0;
public $Proxy0(InvocationHandler var1) throws Exception {
super(var1);
}
public final boolean equals(Object var1) throws Exception {
try {
return (Boolean) super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final void printName() throws Exception {
try {
super.h.invoke(this, m3, (Object[]) null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
}
关键结构解读:
- 继承自
java.lang.reflect.Proxy:JDK 代理类必须继承 Proxy(这是 JDK 动态代理只能基于接口的根本原因——Java 单继承,代理类已经”占用”了继承位); - 构造函数接收
InvocationHandler:传给Proxy(InvocationHandler h),保存为super.h; - 每个接口方法对应一个静态 Method 成员(
m1/m2/m3…); - 重写的接口方法全部调用
super.h.invoke(this, mX, args):所有业务逻辑都汇聚到 InvocationHandler 的invoke方法——这就是 AOP 拦截的底层入口。
代理模式的应用
| 场景 | 说明 |
|---|---|
| Spring AOP | 基于 JDK 动态代理(有接口)或 CGLIB(无接口) |
| MyBatis Mapper | 接口的动态代理实现(MapperProxy) |
| 远程代理 | RPC/Dubbo 中代理远程服务调用 |
| 安全代理 | 访问控制、权限校验 |
| 日志/性能监控 | 方法级日志、耗时统计 |
小结
- 代理模式为其他对象提供代理,控制访问,可以保护与增强目标对象。
- 静态代理编译期确定,一接口一代理,容易代理类爆炸。
- JDK 动态代理运行期生成:代理类继承
Proxy并实现目标接口,构造器接收InvocationHandler,每个接口方法都转发到invoke()。 - 用
-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true可导出生成的代理类研究。 - 代价:类数目增加、请求变慢、系统复杂度上升。