代理模式是Java开发者绕不开的“硬骨头”。很多技术进阶学习者、在校学生和面试备考者在学习时会遇到这样的困惑:代码里用了代理,但说不清静态代理和动态代理的本质区别;面试被问到Spring AOP的底层实现时,只知道“用了动态代理”,却答不出JDK代理和CGLIB的区别。本文将借助AI范文助手高效整合技术资料,从痛点出发,由浅入深地讲透代理模式的完整知识链路,辅以代码示例和高频面试题,帮助读者真正掌握这一核心知识点。
一、为什么需要代理模式:从痛点切入

在日常开发中,我们经常遇到这样的需求:在不修改原有业务代码的前提下,为方法添加日志打印、权限校验、性能监控等额外功能。如果直接在业务方法里写log.info(),会带来几个问题——代码重复、业务逻辑被污染、维护成本高-8。代理模式正是为了解决这一痛点而生:通过引入一个“中间人”代理对象,在不入侵原有代码的前提下,完成功能的扩展。
二、核心概念讲解:代理模式

代理模式(Proxy Pattern)是一种结构型设计模式,其核心定义为:为其他对象提供一种代理以控制对这个对象的访问-。简单来说,就是用一个代理对象来替代真实对象处理外部请求,在代理过程中可以插入额外的逻辑。
生活化类比:想象一位明星(真实对象)和经纪人(代理对象)。粉丝来信都由经纪人先过目筛选,经纪人可以在转交信件前进行内容审查、分类整理,甚至直接拒绝某些来信,而明星只需要处理经过筛选后的邮件-14。这就是代理模式在现实中的体现。
三、关联概念讲解:静态代理
静态代理是代理模式中最基础的实现方式,核心特点是代理类在编译期就已确定,与目标类一一对应,就像为某个明星配备的“专属经纪人”,只服务这一个对象-8。
代码示例(给用户服务类加日志):
// 1. 业务接口 public interface UserService { void addUser(String username); void deleteUser(String username); } // 2. 目标类(真实干活的对象) public class UserServiceImpl implements UserService { @Override public void addUser(String username) { System.out.println("数据库新增用户:" + username); } @Override public void deleteUser(String username) { System.out.println("数据库删除用户:" + username); } } // 3. 静态代理类 public class UserServiceProxy implements UserService { private final UserService target; // 持有目标类引用 public UserServiceProxy(UserService target) { this.target = target; } @Override public void addUser(String username) { System.out.println("〖日志〗开始执行addUser"); // 前置增强 target.addUser(username); // 调用核心业务 System.out.println("〖日志〗addUser执行完毕"); // 后置增强 } // deleteUser同理... }
缺点分析:接口每新增一个方法,代理类必须同步修改;N个目标类就需要编写N个代理类,代码量呈爆炸式增长,维护困难-14。
四、关联概念讲解:动态代理
动态代理将代理类的生成时机推迟到程序运行阶段,JVM会在内存中动态构建代理类的字节码并加载-14。这就解决了静态代理“一个目标类对应一个代理类”的痛点。
Java生态中主流动态代理技术分为两种:
JDK动态代理:基于Java反射机制实现,要求被代理对象实现至少一个接口。核心依赖java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口-25。
代码示例:
// InvocationHandler实现 public class LogInvocationHandler implements InvocationHandler { private final Object target; public LogInvocationHandler(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; } } // 生成代理对象 UserService proxy = (UserService) Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), new LogInvocationHandler(target) );
工作原理:Proxy.newProxyInstance()在内存中拼装生成实现指定接口的Java类字节码,然后通过类加载器加载进JVM,最后通过反射调用构造函数生成代理实例-20。对代理类所有方法的调用都会转发到InvocationHandler.invoke()方法中-20。
CGLIB动态代理:对于没有实现接口的类,JDK动态代理无能为力。CGLIB(Code Generation Library)通过ASM字节码框架为指定类生成一个子类,在子类中覆盖并增强父类方法-。需要注意的是,final类和final方法无法被CGLIB代理,因为Java中final类不可被继承,final方法不可被覆盖-29。
五、概念关系与区别总结
| 对比项 | 静态代理 | JDK动态代理 | CGLIB动态代理 |
|---|---|---|---|
| 代理方式 | 编译期手动编写 | 运行时反射+Proxy | 运行时ASM字节码生成 |
| 必要条件 | 实现相同接口 | 必须有接口 | 类不能是final |
| 底层技术 | 直接调用 | 反射 | 继承+字节码增强 |
| 灵活性 | 差,一对一 | 好,一对多 | 好,一对多 |
| 适用场景 | 业务固定、方法少 | 有接口的类 | 无接口的类 |
一句话概括:静态代理是“写死”的专属中间人,动态代理是“运行时生成”的万能中间人——JDK代理代理接口,CGLIB代理类。
六、底层原理支撑
动态代理的底层依赖两大核心技术:反射机制和字节码操作。JDK动态代理利用反射在运行时动态获取类的信息并调用方法;CGLIB则利用ASM字节码操作框架,在运行时动态生成目标类的子类字节码-29。这两项技术共同支撑了动态代理的上层功能实现,也是Spring AOP等框架的基石。
七、高频面试题与参考答案
Q1:静态代理和动态代理的区别是什么?
静态代理在编译期就已确定代理类,需要手动为每个目标类编写一个代理类,代码冗余且维护困难。动态代理在运行期由JVM动态生成代理类字节码,一份代码可为多个目标类提供代理服务,灵活性强。
Q2:JDK动态代理和CGLIB有什么区别?
JDK动态代理基于接口,只能代理实现了接口的类,底层利用反射+Proxy实现。CGLIB基于继承,通过ASM字节码框架生成目标类的子类,可以代理没有实现接口的类,但无法代理final类和方法。Spring AOP默认优先使用JDK动态代理,目标类无接口时自动切换为CGLIB-30。
Q3:Spring AOP为什么需要两种代理方式?
Spring AOP需要为不同场景提供代理支持:当目标Bean实现了接口时,Spring优先使用JDK动态代理(性能更好、无需额外依赖);当目标Bean没有实现接口时,Spring自动切换到CGLIB代理(通过继承实现)。开发者也可通过配置强制使用CGLIB-33。
Q4:动态代理有哪些优缺点?
优点:一个动态代理类可解决多个静态代理的问题,避免重复代码,灵活性更强。缺点:通过反射调用方法,相比静态代理的直接调用效率略低;JDK动态代理受Java单继承限制,只能代理接口-41。
八、结尾总结
本文系统梳理了代理模式的完整知识链路:从痛点场景出发,认识了静态代理的实现方式与局限性,理解了动态代理如何解决静态代理的缺陷,区分了JDK动态代理与CGLIB的适用场景,并掌握了高频面试考点。核心在于理解“静态代理是编译期确定、一对一绑定”与“动态代理是运行期生成、一对多服务”的本质差异。建议读者动手运行本文示例代码,加深对反射调用和字节码生成机制的理解。
下期预告:将深入讲解Spring AOP如何基于动态代理实现横切关注点的统一管理,敬请期待!
扫一扫微信交流