你有没有遇到过这样的需求?
👉 想给某个方法加个耗时统计,但不想改原有代码?
👉 想在每个业务方法执行前后打印日志,难道要每个方法都手动加 log.info()?
👉 面试官问:“Spring 的 AOP 是怎么实现的?” 你心里一紧,只憋出一句:“嗯……用了代理?”
带你彻底搞懂 Java 动态代理(Dynamic Proxy)!
它就像给对象装了个“影分身”,
你调用的是它,实际执行的却是另一个“幕后黑手”——
而且这一切,在运行时自动生成,不用你手写一行代理类!
一、什么是动态代理?—— 对象的“替身演员”
想象一下,明星(真实对象)很忙,不能亲自处理所有事。
于是他找了个经纪人(代理),对外说:“有事找我!”
粉丝(调用者)以为在跟明星打交道,
其实是经纪人在接电话、谈合同、安排行程……
必要时,经纪人才会去“通知”明星本人。
在 Java 世界里:
- 明星 = 真实对象(Real Object)
- 粉丝 = 调用者(Client)
- 经纪人 = 代理对象(Proxy)
✅ 动态代理 = 在程序运行时,自动生成一个“代理类”,代替真实对象处理请求
二、Java 两种动态代理方式
1. JDK 动态代理 —— 基于接口(最常用)
要求:被代理的类必须实现至少一个接口。
🎯 核心三件套:
💡 举个例子:给服务加日志和性能监控
// 1. 定义接口
public interface UserService {
void saveUser(String name);
String getUser(int id);
}
// 2. 真实实现类(明星本人)
public class UserServiceImpl implements UserService {
@Override
public void saveUser(String name) {
System.out.println("正在保存用户:" + name);
// 模拟耗时
try { Thread.sleep(100); } catch (InterruptedException e) {}
}
@Override
public String getUser(int id) {
System.out.println("正在查询用户:" + id);
return "用户" + id;
}
}
// 3. 经纪人(InvocationHandler)—— 核心!拦截所有方法调用
public class LoggingInvocationHandler implements InvocationHandler {
private Object target; // 真实对象
public LoggingInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 【前置增强】方法执行前
String methodName = method.getName();
System.out.println("【代理拦截】即将执行方法:" + methodName + ",参数:" + Arrays.toString(args));
long start = System.currentTimeMillis();
// 【核心】调用真实对象的方法
Object result = method.invoke(target, args);
// 【后置增强】方法执行后
long cost = System.currentTimeMillis() – start;
System.out.println("【代理拦截】方法 " + methodName + " 执行完成,耗时:" + cost + "ms,返回值:" + result);
return result;
}
}
// 4. 客户端使用(粉丝找明星)
public class Client {
public static void main(String[] args) {
// 真实对象
UserService realService = new UserServiceImpl();
// 创建代理对象(经纪人上岗!)
UserService proxyService = (UserService) Proxy.newProxyInstance(
realService.getClass().getClassLoader(), // 类加载器
realService.getClass().getInterfaces(), // 代理哪些接口
new LoggingInvocationHandler(realService) // 代理逻辑
);
// 调用者以为在调真实对象,其实是代理在处理
proxyService.saveUser("张三");
proxyService.getUser(1001);
}
}
✅ 输出结果:
【代理拦截】即将执行方法:saveUser,参数:[张三]
正在保存用户:张三
【代理拦截】方法 saveUser 执行完成,耗时:105ms,返回值:null
【代理拦截】即将执行方法:getUser,参数:[1001]
正在查询用户:1001
【代理拦截】方法 getUser 执行完成,耗时:2ms,返回值:用户1001
💡 看!我们没改一行 UserServiceImpl 的代码,就实现了日志和性能监控!
2. CGLIB 动态代理 —— 基于继承(无接口也能代理)
问题:如果类没实现接口,JDK 代理就歇菜了。
解决方案:CGLIB(Code Generation Library)
它通过继承真实类,生成子类作为代理。
✅ 优点:
- 不需要接口,直接代理普通类。
- Spring 默认用它处理没有接口的 Bean。
❌ 缺点:
- 不能代理 final 类或 final 方法(无法继承或重写)。
- 需要引入额外依赖(Spring 已内置)。
💡 简单示例(Spring 中常见):
// 一个没有接口的类
public class OrderService {
public void createOrder(String orderId) {
System.out.println("创建订单:" + orderId);
}
}
Spring AOP 会生成一个代理类,像这样:
// 伪代码:CGLIB 生成的代理类
public class OrderService$$EnhancerByCGLIB extends OrderService {
private MethodInterceptor interceptor;
@Override
public void createOrder(String orderId) {
// 调用拦截器(类似 InvocationHandler)
interceptor.intercept(this, 方法对象, 参数, 原方法引用);
}
}
三、动态代理 vs 静态代理 —— 手工打造 vs 工厂量产
静态代理:每个接口都要写一个代理类,累死!
public class UserServiceProxy implements UserService {
private UserService target;
public UserServiceProxy(UserService target) {
this.target = target;
}
@Override
public void saveUser(String name) {
System.out.println("前置日志");
target.saveUser(name);
System.out.println("后置日志");
}
// getUser 也要写一遍……重复代码!
}
动态代理:一个 InvocationHandler 通吃所有接口!
✅ 动态代理 = 高度复用,无需提前写代理类,运行时自动生成
四、高频问题 & 高分回答
Q1: 动态代理的两种方式有什么区别?
实现机制 | 基于接口,生成实现类 | 基于继承,生成子类 |
是否需要接口 | 必须有 | 不需要 |
代理 final 类 | 不行 | 不行 |
性能 | JDK 1.8+ 已优化,性能好 | 略快(直接调用) |
Spring 默认策略 | 有接口用 JDK | 无接口用 CGLIB |
💡 加分点:
“Spring AOP 默认优先用 JDK 动态代理(基于接口),更安全;如果没有接口,则用 CGLIB。”
Q2: Proxy.newProxyInstance 返回的是什么对象?
答:
它返回一个实现了指定接口的代理类实例。
这个类是运行时动态生成的,名字类似 com.sun.proxy.$Proxy0。
它持有 InvocationHandler 的引用,所有方法调用都会被转发到 invoke() 方法中。
Q3: 动态代理在实际项目中用在哪?
答:
💬 我在项目中用 AOP + 动态代理统一处理接口耗时监控和操作日志,减少了大量重复代码。
Q4: 为什么 JDK 动态代理必须基于接口?
答:
因为生成的代理类需要“伪装”成真实对象。
Java 是单继承,如果用继承方式,就不能再继承其他类了。
而接口可以实现多个,所以 JDK 选择“实现接口”来生成代理类,避免继承冲突,更灵活。
✅ 总结:一张表搞懂核心要点
什么是动态代理 | 运行时自动生成代理类,拦截方法调用 |
JDK 代理要求 | 被代理类必须实现接口 |
核心接口 | InvocationHandler + Proxy.newProxyInstance |
CGLIB 特点 | 基于继承,无需接口,但不能代理 final 类 |
实际应用场景 | Spring AOP、RPC、事务管理、日志监控 |
面试怎么说 | “我用 AOP 做了统一日志和性能监控,底层就是动态代理” |
🔚 最后一句话
动态代理不是“黑魔法”,而是“解耦利器”。
它让你把核心业务和横切逻辑(日志、事务、安全)彻底分开,
实现“代码零侵入”,这才是 AOP 的真正魅力!
希望这篇能帮你彻底搞懂 Java 动态代理!
评论前必须登录!
注册