北京时间2026年4月8日
开篇引入

在Java技术体系中,代理模式(Proxy Pattern)是应用最广泛、面试出现频率最高的设计模式之一。AI中心助手发现,大量开发者在日常编码中都会用到动态代理,比如Spring AOP、RPC框架、MyBatis等,但往往停留在“会用”层面,一旦被问到“静态代理和动态代理有什么区别”“JDK动态代理为什么只能代理接口”“Spring AOP底层是如何实现的”,就答不上来了。本文将从痛点出发,由浅入深讲解代理模式的核心概念、代码实现、底层原理及高频面试题,帮你建立完整的知识链路。
一、痛点切入:为什么需要代理模式

先来看一个日常开发中的场景。假设有一个用户服务,需要在每个方法执行前后打印日志:
public class UserService { public void addUser(String name) { System.out.println("【日志】开始添加用户: " + name); // 核心业务:添加用户 System.out.println("添加用户成功"); System.out.println("【日志】添加用户结束"); } public void deleteUser(Long id) { System.out.println("【日志】开始删除用户: " + id); // 核心业务:删除用户 System.out.println("删除用户成功"); System.out.println("【日志】删除用户结束"); } }
这段代码存在明显问题:
代码冗余:每个方法都要重复写日志代码,且日志逻辑变更时需要逐一修改
耦合度高:日志代码与业务代码耦合在一起,违反了开闭原则(Open-Closed Principle)——对扩展开放,对修改关闭-6
维护困难:如果要添加权限校验、性能监控等增强逻辑,代码会变得更加臃肿
有没有一种方式可以在不修改原有业务类的前提下,给方法动态添加增强逻辑?答案是——代理模式。
代理模式的核心价值正是解耦核心业务逻辑与横切关注点(如日志、权限、事务等),在不修改目标对象代码的情况下实现功能扩展-45。
二、核心概念讲解:代理模式(Proxy Pattern)
定义
代理模式(Proxy Pattern)是一种结构型设计模式(Structural Pattern),它为目标对象提供一个代理对象,并由代理对象控制对原对象的引用。客户端不直接调用目标对象,而是通过代理对象间接访问,代理可以在请求传递到目标对象前后执行额外的操作--。
拆解关键词
代理(Proxy) :代替真实对象处理请求的中间对象
控制访问:代理可以决定是否、何时、以何种方式将请求转发给真实对象
增强功能:在真实对象方法调用前后执行额外逻辑(日志、权限、缓存等)
生活化类比
信用卡就是银行账户的代理。 你去商场购物时,直接用信用卡支付,不用回家取现金。信用卡在“支付”这个接口上与现金保持一致,同时提供了额外的好处——安全、便捷、可记录消费明细-54。
代理模式的结构
代理模式由三个核心角色组成-37:
| 角色 | 说明 | 示例 |
|---|---|---|
| 抽象主题(Subject) | 定义真实对象和代理对象的共同接口 | Service接口 |
| 真实主题(RealSubject) | 真正执行业务逻辑的对象 | UserService |
| 代理(Proxy) | 持有真实对象的引用,控制访问并增强功能 | UserServiceProxy |
执行流程:客户端 → 代理对象方法 → 前置增强逻辑 → 真实对象核心方法 → 后置增强逻辑 → 返回结果给客户端。
三、关联概念讲解:静态代理与动态代理
代理模式根据代理类生成时机的不同,分为静态代理和动态代理两种-。
3.1 静态代理(Static Proxy)
定义:在编译期就已经确定代理类,程序员需要手动编写代理类代码,代理类与真实类共同实现同一个接口-。
// 抽象主题 public interface UserService { void addUser(String name); } // 真实主题 public class UserServiceImpl implements UserService { @Override public void addUser(String name) { System.out.println("添加用户: " + name); } } // 静态代理类 public class UserServiceStaticProxy implements UserService { private UserService target; public UserServiceStaticProxy(UserService target) { this.target = target; } @Override public void addUser(String name) { // 前置增强:日志 System.out.println("[日志] 开始添加用户: " + name); // 调用真实对象 target.addUser(name); // 后置增强 System.out.println("[日志] 添加用户结束"); } } // 客户端调用 public class Client { public static void main(String[] args) { UserService userService = new UserServiceImpl(); UserService proxy = new UserServiceStaticProxy(userService); proxy.addUser("张三"); } }
优点:实现简单、结构清晰、编译时类型检查-6-10。
缺点:一个代理类只能代理一个接口,当有多个业务类需要增强时,需要编写大量重复的代理类,代码冗余、扩展性差--9。
3.2 动态代理(Dynamic Proxy)
定义:在程序运行时通过反射机制动态生成代理类的字节码,无需手动编写代理类代码。一个动态代理类可以为任意多个真实类提供代理服务-41。
Java中动态代理主要有两种实现方式:
JDK动态代理(基于接口)
要求目标类必须实现接口。核心API:java.lang.reflect.Proxy + java.lang.reflect.InvocationHandler-19。
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; // 动态代理处理器 public class LogInvocationHandler implements InvocationHandler { private Object target; public LogInvocationHandler(Object target) { this.target = target; } @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) { UserService target = new UserServiceImpl(); UserService proxy = (UserService) Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), new LogInvocationHandler(target) ); proxy.addUser("李四"); } }
核心步骤:
调用
Proxy.newProxyInstance()传入类加载器、接口列表、InvocationHandlerJDK在内存中动态拼接代理类的字节码(格式固定,实现指定接口)
将字节码加载进JVM,通过反射生成代理类实例-27
CGLIB动态代理(基于继承)
CGLIB(Code Generation Library)通过字节码技术为目标类创建子类,在子类中拦截父类方法的调用。优势:不需要接口,但final类和final方法无法代理--17。
// 真实主题(不需要实现接口) public class UserService { public void addUser(String name) { System.out.println("添加用户: " + name); } } // CGLIB动态代理 import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; public class CglibLogInterceptor implements MethodInterceptor { private Object target; public CglibLogInterceptor(Object target) { this.target = target; } public Object getProxy() { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(target.getClass()); enhancer.setCallback(this); return enhancer.create(); } @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("[日志] 开始调用: " + method.getName()); Object result = method.invoke(target, args); System.out.println("[日志] 调用结束: " + method.getName()); return result; } }
四、静态代理与动态代理的区别总结
| 对比维度 | 静态代理 | JDK动态代理 | CGLIB动态代理 |
|---|---|---|---|
| 代理类生成时机 | 编译期 | 运行时 | 运行时 |
| 是否需要接口 | 需要 | 必须需要 | 不需要 |
| 实现原理 | 手动编码实现 | 反射+动态生成字节码 | 字节码技术创建子类 |
| 代码量 | 多,每个目标类一个代理 | 少,一个InvocationHandler复用 | 少,一个MethodInterceptor复用 |
| 性能 | 调用时直接执行,性能高 | 有反射开销 | 性能优于JDK,但创建开销更大 |
| 灵活性 | 差,接口变更需同步修改 | 高 | 高 |
| 适用场景 | 目标类少、功能固定 | 目标类实现了接口 | 目标类无接口或需代理final之外的类 |
一句话总结:静态代理是“写死的代理”,动态代理是“生成的代理”;静态代理是思想,动态代理是实现手段;JDK动态代理基于接口,CGLIB动态代理基于继承。
五、底层原理与关键技术支撑
JDK动态代理的核心底层技术是动态生成字节码 + 反射机制-30。
具体实现分为三步-27:
拼凑生成字节码:根据传入的接口列表,在内存中拼接出一个实现了这些接口的Java类字节码。该类所有方法实现中都会调用
InvocationHandler.invoke()。类加载:将内存中生成的字节码加载进JVM,通过
ClassLoader.defineClass()生成代理类的Class<?>对象。反射实例化:通过反射调用代理类的构造函数(参数为InvocationHandler),生成代理类实例。
注意:多次调用Proxy.newProxyInstance(),只要前两个参数(类加载器和接口列表)相同,就会走缓存,避免重复生成字节码-27。
CGLIB动态代理则基于字节码操作框架ASM,通过动态创建目标类的子类来实现,在子类中使用MethodInterceptor拦截父类方法的调用-17。
这两个底层技术是理解动态代理机制的关键,也为后续学习Spring AOP源码打下基础。Spring AOP正是基于JDK动态代理和CGLIB动态代理来实现的-。
六、高频面试题与参考答案
Q1:什么是代理模式?它的核心作用是什么?
标准答案:代理模式是一种结构型设计模式,为目标对象提供一个代理对象,由代理对象控制对原对象的引用。核心作用是在不修改目标对象原有代码的前提下,通过代理对象实现在核心方法前后添加额外的业务逻辑(如日志、权限、事务等),符合设计模式的开闭原则-37-41。
Q2:静态代理和动态代理有什么区别?
标准答案:主要区别体现在以下三点--9:
生成时机不同:静态代理在编译期手动编写代理类,动态代理在运行期由JVM动态生成字节码
代码量不同:静态代理需要为每个目标类单独编写代理类,代码冗余;动态代理一个处理器类可为任意多个目标类服务
灵活性不同:动态代理更加灵活,支持运行时动态增强
Q3:JDK动态代理为什么只能代理接口?
标准答案:JDK动态代理是基于接口实现的。Proxy.newProxyInstance()方法需要传入接口列表,生成的代理类实现了这些接口。代理对象的类型由接口列表决定,代理对象的方法调用会转发到InvocationHandler.invoke()。由于Java是单继承,生成的代理类已经继承了Proxy类,无法再继承目标类,所以只能通过实现接口的方式代理-19。
Q4:JDK动态代理和CGLIB动态代理如何选择?Spring AOP是如何决定的?
标准答案:JDK动态代理要求目标类实现接口,性能适中;CGLIB动态代理不需要接口,通过继承实现,创建代理对象开销更大但调用性能更高。Spring AOP的默认策略是:如果目标类实现了接口,优先使用JDK动态代理;如果目标类没有实现接口,则使用CGLIB代理--50。也可通过配置强制使用CGLIB。
Q5:代理模式和装饰器模式有什么区别?
标准答案:两者结构相似,但设计意图不同。代理模式的目的是控制对目标对象的访问,通常由代理管理目标对象的生命周期;装饰器模式的目的是动态地为对象添加额外职责,装饰器和被装饰对象通常是独立的,调用方可以灵活组合-。
结尾总结
本文核心知识回顾:
| 核心点 | 关键内容 |
|---|---|
| ✅ 代理模式定义 | 提供代理对象,控制对原对象的访问,在不修改目标代码的前提下增强功能 |
| ✅ 静态代理 | 编译期手动编写代理类,简单但冗余,适合目标类少的场景 |
| ✅ JDK动态代理 | 运行时生成字节码+反射,要求目标类实现接口,一个Handler复用 |
| ✅ CGLIB动态代理 | 字节码技术创建子类,无需接口,final类和方法无法代理 |
| ✅ 底层原理 | JDK:动态字节码生成+反射;CGLIB:ASM字节码框架 |
| ✅ 面试重点 | 两者区别、JDK为何只能代理接口、Spring AOP的选择策略 |
易错点提醒:不要混淆代理模式和装饰器模式;不要认为CGLIB比JDK动态代理“更好”,两者各有适用场景;JDK动态代理的局限性在于接口而非类,这在面试中是高频考点。
进阶预告:下一篇将深入Spring AOP源码,剖析代理对象的创建时机与流程,讲解@EnableAspectJAutoProxy注解背后的机制,帮助读者彻底吃透AOP底层实现。敬请期待!
📌 本文基于北京时间2026年4月8日的最新技术资料整理,代码示例可在JDK 1.8+和Spring 5.x环境下运行。如需更详细的技术交流,欢迎在评论区留言讨论。
扫一扫微信交流