📅 发布时间:北京时间 2026年4月9日
🎯 目标读者: 技术入门/进阶学习者、在校学生、面试备考者、Java后端开发工程师
📝 文章定位: 技术科普 + 原理讲解 + 代码示例 + 面试要点,兼顾易懂性与实用性
一、开篇引入:为什么面试官总爱问代理模式?

在Java后端开发中,代理模式(Proxy Pattern) 是每一位开发者绕不开的核心知识点。它是 Spring AOP(Aspect-Oriented Programming,面向切面编程) 的底层实现基石,也是面试中频率最高的考点之一。无论是日志记录、事务管理、权限校验,还是远程调用(RPC)、延迟加载,代理模式都在背后默默支撑着这些框架级功能。
很多学习者面临这样的困境:会用Spring的@Transactional注解,却说不清AOP底层是怎么“织入”增强逻辑的;听说过JDK动态代理和CGLIB,却讲不出两者的区别;面试中被问到“代理模式和装饰器模式有什么区别”时,更是大脑一片空白。

本文将由浅入深,带你理清从静态代理到动态代理的完整知识链路,并通过代码示例、原理剖析、面试考点三位一体的方式,帮助你真正理解代理模式的本质。
二、痛点切入:为什么需要代理模式?
2.1 传统方式的困境
假设我们需要为UserService中的每个方法添加日志记录。最直接的做法是:在每一个业务方法内部手动添加日志代码。
public class UserServiceImpl implements UserService { @Override public void createUser(String name) { // 手动添加日志——代码侵入 System.out.println("[日志] 开始创建用户: " + name); System.out.println("创建用户: " + name); // 核心业务 System.out.println("[日志] 创建用户完成"); } @Override public void deleteUser(int id) { // 又是重复的日志代码 System.out.println("[日志] 开始删除用户: " + id); System.out.println("删除用户: " + id); // 核心业务 System.out.println("[日志] 删除用户完成"); } }
如果UserService有10个方法,就要重复写10遍相同的日志逻辑;如果有多个Service(如OrderService、ProductService),重复的代码量会呈指数级增长。
2.2 传统方式的致命缺陷
耦合度高:日志、事务等横切逻辑与核心业务逻辑混在一起,严重违反单一职责原则。
代码冗余:相同的增强代码在每个方法中反复出现,维护成本极高。
扩展性差:如果有一天需要将日志从控制台输出改为写入文件,需要修改每一个业务方法。
可读性低:核心业务逻辑被大量非业务代码“淹没”,难以快速理解业务意图。
2.3 代理模式的解决思路
代理模式的核心思想是:不修改原有代码,通过引入一个“代理对象”来控制对真实对象的访问,在代理对象中统一处理横切逻辑。
这就像现实中的明星经纪人——粉丝想联系明星,不直接找明星本人,而是通过经纪人。经纪人可以统一处理预约、合同、宣传等事务,明星只需要专注于自己的演艺事业-2。代理模式正是这种思想在编程中的体现。
三、核心概念讲解:代理模式(Proxy Pattern)
3.1 标准定义
代理模式(Proxy Pattern) 是一种结构型设计模式(Structural Design Pattern) ,它为另一个对象提供一个替身或占位符以控制对这个对象的访问-48。
3.2 拆解关键词
代理对象(Proxy) :相当于“经纪人”,负责控制访问和添加增强逻辑。
真实对象(Real Subject) :相当于“明星”,负责执行核心业务逻辑。
抽象主题(Subject) :代理对象和真实对象共同实现的接口,确保两者可以互相替换。
3.3 生活化类比
你去银行取钱:
银行柜台的工作人员就是代理;
银行的后台金库系统就是真实对象;
工作人员在帮你取钱之前,会先核对你的身份证、输入密码、检查账户余额——这些就是前置增强逻辑;
取完钱后,工作人员会打印小票——这就是后置增强逻辑;
而你作为客户,根本不需要知道金库的具体位置和开锁方式,你只和工作人员打交道。
这个类比清晰地展示了代理模式的核心价值:隔离复杂性、统一增强逻辑、控制访问权限。
3.4 代理模式的核心作用
| 作用 | 说明 |
|---|---|
| 控制访问 | 在调用真实对象前进行权限校验、身份验证 |
| 功能增强 | 不修改原代码,添加日志、事务、缓存、性能监控等 |
| 延迟加载 | 当创建真实对象开销较大时,先返回轻量级代理,真正使用时才创建 |
| 远程代理 | 为位于不同地址空间的对象提供本地代表(如RPC调用)-48 |
四、关联概念讲解:静态代理 vs 动态代理
Java中的代理按照代理类的生成时机,分为静态代理和动态代理两大类-1-49。
4.1 静态代理(Static Proxy)
定义:静态代理是在编译期就已经确定代理类和被代理类的关系,代理类需要手动编写,与被代理类实现相同的接口-1。
代码示例:
// 1. 抽象主题:业务接口 public interface UserService { void createUser(String name); void deleteUser(int id); } // 2. 真实主题:核心业务实现 public class UserServiceImpl implements UserService { @Override public void createUser(String name) { System.out.println("创建用户: " + name); } @Override public void deleteUser(int id) { System.out.println("删除用户: " + id); } } // 3. 代理类:手动编写,实现同一接口 public class UserServiceStaticProxy implements UserService { private UserService target; // 持有真实对象的引用 public UserServiceStaticProxy(UserService target) { this.target = target; } @Override public void createUser(String name) { System.out.println("[静态代理] 前置日志"); target.createUser(name); System.out.println("[静态代理] 后置日志"); } @Override public void deleteUser(int id) { System.out.println("[静态代理] 前置日志"); target.deleteUser(id); System.out.println("[静态代理] 后置日志"); } } // 4. 客户端使用 public class Client { public static void main(String[] args) { UserService realService = new UserServiceImpl(); UserService proxy = new UserServiceStaticProxy(realService); proxy.createUser("张三"); // 通过代理调用 } }
静态代理的优缺点:
✅ 优点:实现简单、逻辑清晰、编译期类型安全-1。
❌ 缺点:每个被代理类都需要一个对应的代理类,导致类爆炸;接口每增加一个方法,所有代理类都需要同步修改,维护成本高-1。
4.2 动态代理(Dynamic Proxy)
定义:动态代理是在运行时动态生成代理类,无需手动编写代理类代码,一个动态代理类可以为任意多个真实类提供代理服务-1。
动态代理又分为两种实现方式:
4.2.1 JDK动态代理
原理:基于Java反射机制,要求目标类必须实现一个或多个接口。通过java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口实现-1。
// 动态代理处理器 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("[JDK动态代理] 前置日志,方法:" + method.getName()); Object result = method.invoke(target, args); // 调用真实对象的方法 System.out.println("[JDK动态代理] 后置日志"); return result; } } // 使用动态代理 public class Client { public static void main(String[] args) { UserService realService = new UserServiceImpl(); // 一行代码生成代理对象 UserService proxy = (UserService) Proxy.newProxyInstance( realService.getClass().getClassLoader(), realService.getClass().getInterfaces(), new LogInvocationHandler(realService) ); proxy.createUser("张三"); // 调用代理,自动触发增强逻辑 } }
4.2.2 CGLIB动态代理
原理:基于字节码技术,通过动态创建目标类的子类来实现代理,不要求目标类实现接口。底层依赖ASM字节码操作框架-33。
// CGLIB代理拦截器 public class CglibMethodInterceptor implements MethodInterceptor { private Object target; public CglibMethodInterceptor(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("[CGLIB] 前置日志"); Object result = proxy.invokeSuper(obj, args); // 调用父类方法 System.out.println("[CGLIB] 后置日志"); return result; } }
五、概念关系与区别总结
5.1 三者的逻辑关系
代理模式是一种设计思想(概念层);
静态代理是代理模式的一种具体实现方式,在编译期完成;
动态代理是代理模式的另一种具体实现方式,在运行时完成;
JDK动态代理和CGLIB动态代理是动态代理的两种技术实现路径。
一句话概括:静态代理在编译期写死代理关系,动态代理在运行时动态生成代理;动态代理中,JDK靠接口、CGLIB靠继承。
5.2 详细对比表
| 对比维度 | 静态代理 | JDK动态代理 | CGLIB动态代理 |
|---|---|---|---|
| 代理类生成时机 | 编译期 | 运行时 | 运行时 |
| 是否需手动编写代理类 | 需要 | 不需要 | 不需要 |
| 对目标类的要求 | 需实现接口 | 必须实现接口 | 无需接口,但不能代理final类/方法 |
| 实现原理 | 代码显式调用 | 反射 + 动态字节码 | ASM字节码生成子类 |
| 性能特点 | 直接调用,性能高 | 反射调用,有一定开销 | 生成时耗时,运行时较快-29 |
| 代码冗余度 | 高(每个类一个代理) | 低(一个处理器处理所有) | 低 |
| Spring AOP默认策略 | 不使用 | 目标有接口时优先 | 目标无接口时自动切换-39 |
六、底层原理 / 技术支撑点
6.1 JDK动态代理的底层原理
JDK动态代理的底层依赖Java反射机制。Proxy.newProxyInstance()方法的执行过程包含三个关键步骤-19:
生成字节码:根据传入的接口数组,在内存中动态拼装一个实现这些接口的代理类的字节码。生成的代理类继承自
java.lang.reflect.Proxy。类加载:使用
ClassLoader将内存中的字节码加载到JVM中,生成代理类的Class对象。实例化:通过反射调用代理类的构造函数(该构造函数接收
InvocationHandler参数),生成代理实例。
生成的代理类的全类名格式类似jdk.proxy1.$Proxy0,在日志中看到这样的类名时,就说明正在使用JDK动态代理-19。
6.2 CGLIB动态代理的底层原理
CGLIB底层依赖ASM字节码操作框架,其核心机制是-33:
创建子类:通过
Enhancer类,动态生成目标类的子类。方法拦截:在生成的子类中,覆盖所有非
final的方法,将方法调用委托给MethodInterceptor。继承代理:CGLIB基于继承实现,所以无法代理
final类,也无法代理final或static方法。
💡 这两个底层知识点是Spring AOP面试的高频追问点,理解它们对于后续深入学习Spring源码至关重要。
七、Spring AOP中的应用实践
7.1 Spring AOP的代理选择策略
Spring AOP的底层实现正是基于上述动态代理机制。Spring通过DefaultAopProxyFactory自动判断-39:
如果目标类实现了接口,默认使用 JDK动态代理;
如果目标类没有实现接口,则自动切换到 CGLIB动态代理;
也可以通过配置
spring.aop.proxy-target-class=true强制使用CGLIB。
7.2 Spring AOP代码示例
@Aspect @Component public class LogAspect { @Before("execution( com.example.service..(..))") public void beforeMethod(JoinPoint joinPoint) { System.out.println("[Spring AOP] 前置通知,方法:" + joinPoint.getSignature().getName()); } @AfterReturning(pointcut = "execution( com.example.service..(..))", returning = "result") public void afterReturning(JoinPoint joinPoint, Object result) { System.out.println("[Spring AOP] 后置通知,返回结果:" + result); } }
上面的代码只是“声明”了切面逻辑,实际运行时Spring会动态生成代理对象,将LogAspect中的增强逻辑织入到目标方法的执行前后。
八、高频面试题与参考答案
Q1:代理模式和装饰器模式有什么区别?(⭐⭐⭐⭐⭐)
参考答案:
代理模式和装饰器模式都是结构型设计模式,都通过组合方式增强功能,但核心目的不同:
代理模式侧重于控制访问——决定“能否调用”“何时调用”,可以阻止调用(如权限不足时直接返回);
装饰器模式侧重于增强功能——总是调用原方法,并在其基础上叠加新功能(如给咖啡加奶、加糖)-48。
简单记忆:代理管“门禁”,装饰管“装修” 。
Q2:JDK动态代理和CGLIB动态代理有什么区别?Spring AOP如何选择?(⭐⭐⭐⭐⭐)
参考答案:
实现原理不同:JDK动态代理基于接口,通过反射生成实现接口的代理类;CGLIB基于继承,通过字节码技术生成目标类的子类。
对目标类的要求不同:JDK要求目标类必须实现接口;CGLIB无需接口,但无法代理final类和方法-42。
性能差异:JDK动态代理在JDK 8+后性能优化明显;CGLIB代理生成时耗时较多,但运行时调用性能略优-29。
Spring AOP的选择策略:目标类有接口时默认使用JDK动态代理,无接口时自动切换为CGLIB;可通过
proxyTargetClass=true强制使用CGLIB-39。
Q3:静态代理有什么缺点?为什么动态代理能解决这些问题?(⭐⭐⭐⭐)
参考答案:
静态代理的缺点:
类爆炸:每个被代理类都需要一个代理类,系统复杂时代理类数量激增;
维护困难:接口每增加一个方法,所有代理类都要同步修改;
代码冗余:相同的增强逻辑在每个代理类中重复编写。
动态代理的解决方案:
一个
InvocationHandler或MethodInterceptor可以为任意多个类提供代理;代理逻辑集中维护,修改一处即可生效;
接口变更时无需修改代理代码,动态代理会自动适配新增的方法。
Q4:CGLIB为什么不能代理final类和方法?(⭐⭐⭐)
参考答案:
CGLIB的实现原理是动态生成目标类的子类,通过继承关系实现代理。在Java中,final类不能被继承,final方法不能被覆盖(重写)。因此CGLIB无法为final类创建子类,也无法覆盖final方法来实现拦截增强-33。
Q5:讲一讲JDK动态代理的底层实现原理?(⭐⭐⭐⭐)
参考答案:
JDK动态代理底层依赖Java反射机制,核心流程分三步-19:
动态生成字节码:
Proxy.newProxyInstance()根据传入的接口数组,在内存中动态拼装一个实现这些接口的代理类的字节码;类加载:使用
ClassLoader将字节码加载到JVM,生成代理类的Class对象;反射实例化:通过反射调用代理类的构造函数(参数为
InvocationHandler)生成代理实例。
生成的代理类继承自java.lang.reflect.Proxy,其所有方法调用都会被转发到InvocationHandler.invoke()方法。如果多次调用且前两个参数相同,JVM会复用已生成的代理类,避免重复生成-19。
九、结尾总结
本文从传统编码方式的痛点出发,系统讲解了Java代理模式的完整知识体系:
| 知识点 | 核心要点 |
|---|---|
| 什么是代理模式 | 为对象提供代理以控制访问,实现功能增强与业务解耦 |
| 静态代理 | 编译期确定,手动编写代理类,适合简单固定场景,但易造成类爆炸 |
| JDK动态代理 | 运行时生成,依赖接口和反射,一个处理器可为多个类代理 |
| CGLIB动态代理 | 运行时生成子类,无需接口,基于ASM字节码技术,不能代理final类 |
| Spring AOP应用 | 根据目标类是否有接口自动选择代理方式,是AOP的底层引擎 |
| 底层原理 | JDK靠反射+动态字节码生成;CGLIB靠ASM字节码+子类继承 |
| 高频考点 | 代理vs装饰器、JDK vs CGLIB、静态vs动态、底层实现原理 |
📌 重点提示:
面试中回答“代理模式和装饰器模式的区别”时,一定要从目的入手——代理是控制访问,装饰器是增强功能,这是最容易混淆的考点。
说到“动态代理比静态代理好”时,要能说出具体好在哪——代码不冗余、维护成本低、扩展性强。
被追问底层原理时,至少要能说出JDK动态代理依赖反射和
InvocationHandler,CGLIB基于ASM字节码生成子类。
📖 下一篇预告:我们将深入Spring AOP的源码层面,剖析@Transactional注解的事务管理是如何通过动态代理实现的,以及事务失效的常见场景和解决方案。敬请期待!
💬 欢迎在评论区留言交流,如果你有关于代理模式或Spring AOP的其他问题,也欢迎随时提问!
扫一扫微信交流