梦幻AI助手为技术学习者提供全方位的编程知识支持。本文由梦幻AI助手整理完成,带你彻底搞懂Java代理模式——从“只会用”到“懂原理能面试”。
做Java开发时,你是否经常遇到这样的场景:想给Service层的每个业务方法都加上日志打印,结果每个方法里都塞满了log.info(...);想给支付接口做权限校验,又怕直接修改原有逻辑引入Bug;甚至想在不改动老代码的前提下,偷偷给旧系统加个性能监控——这些“在不入侵原有代码的前提下给业务逻辑附加额外功能”的需求,其实都在呼唤同一个解决方案:代理模式。

代理模式是Java开发中必学必会的核心知识点,也是Spring AOP、MyBatis Mapper、Dubbo RPC等主流框架的底层基石。然而很多开发者在实际工作中虽然天天用,却说不出其本质原理,面试时更是“话到嘴边说不出”。本文将从实际问题出发,由浅入深带你搞懂代理模式,掌握静态代理、JDK动态代理、CGLIB动态代理三大实现方式的原理、差异和适用场景。
一、痛点切入:为什么需要代理模式

先看一个典型场景。假设你有一个用户服务,需要在方法执行前后打印日志:
// 目标类:专注核心业务 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); } }
传统做法——直接在业务方法内部混入日志代码:
public class UserServiceImpl implements UserService { @Override public void addUser(String username) { System.out.println("〖日志〗开始执行addUser,参数:" + username); // 侵入业务逻辑 System.out.println("数据库新增用户:" + username); System.out.println("〖日志〗addUser执行完成"); } }
这么做的问题很明显:
❌ 代码冗余:每个方法都要重复写日志代码
❌ 耦合度高:日志逻辑与业务逻辑强耦合,修改日志格式需要改动所有方法
❌ 维护困难:如果需要切换日志框架,改动量巨大
❌ 违背开闭原则:对扩展开放,但对修改没有封闭
代理模式正是为解决这一矛盾而生的设计模式-1。它通过在客户端和目标对象之间引入一个“中间人”——代理对象,在不修改目标类代码的前提下,为方法调用附加额外逻辑。
二、核心概念讲解:静态代理
什么是静态代理?
静态代理(Static Proxy) :代理类的代码在项目编译阶段就已确定,与目标类的字节码一同存在于最终产物中,需为每个被代理类手动编写对应的代理类-。
生活化类比
静态代理就像明星的“专属经纪人” 。一个经纪人只服务一个明星,负责处理行程安排、对外沟通等事务,明星只需专注于唱歌演戏本身-。
静态代理实现步骤
静态代理的核心是代理类和目标类实现相同的接口,代理类持有目标类的引用,在调用方法时插入增强逻辑-1。
步骤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,参数:" + username); // 调用目标类的核心方法 target.addUser(username); // 后置增强 System.out.println("〖日志〗addUser执行完成"); } @Override public void deleteUser(String username) { System.out.println("〖日志〗开始执行deleteUser,参数:" + username); target.deleteUser(username); System.out.println("〖日志〗deleteUser执行完成"); } }
步骤4:客户端使用
public class Client { public static void main(String[] args) { UserService target = new UserServiceImpl(); UserService proxy = new UserServiceProxy(target); proxy.addUser("张三"); // 实际调用的是代理对象的方法 } }
运行结果:
〖日志〗开始执行addUser,参数:张三 数据库新增用户:张三 〖日志〗addUser执行完成
三、关联概念讲解:动态代理
什么是动态代理?
动态代理(Dynamic Proxy) :代理类并非提前编译好,而是在Java程序运行过程中,根据实际业务需求动态创建并加载,无需手动编写代理类代码-。
动态代理通过在运行时动态生成代理类,极大提升了灵活性。一句话总结:静态代理是“编译期绑定”,动态代理是“运行期生成”。
生活化类比
动态代理就像一个“万能中介平台” (比如链家)。你不需要为每个房源单独写一套代理代码,平台会根据你的需求动态匹配房源并处理所有交易流程。
Java动态代理的两种实现方式
1. JDK动态代理
JDK动态代理:基于Java原生的java.lang.reflect.Proxy类和InvocationHandler接口实现,要求目标类必须实现至少一个接口-。
JDK动态代理核心三要素
接口:目标对象必须实现的接口,定义了可被代理的方法
InvocationHandler:方法调用处理器,拦截所有代理方法调用,在其中实现增强逻辑
Proxy:JDK提供的工具类,通过
newProxyInstance()方法生成代理对象-12
JDK动态代理代码示例
目标接口和实现类:
public interface UserService { void addUser(String username); void deleteUser(String username); } 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); } }
实现InvocationHandler(自定义调用处理器) :
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; 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("〖日志〗调用方法:" + method.getName() + ",参数:" + java.util.Arrays.toString(args)); // 通过反射调用目标对象的方法 Object result = method.invoke(target, args); // 后置增强 System.out.println("〖日志〗方法执行完成,返回值:" + result); return result; } }
生成代理对象并调用:
import java.lang.reflect.Proxy; public class JdkProxyDemo { public static void main(String[] args) { // 1. 创建真实目标对象 UserService target = new UserServiceImpl(); // 2. 创建InvocationHandler LogInvocationHandler handler = new LogInvocationHandler(target); // 3. 通过Proxy生成代理对象 // 参数说明: // - loader:类加载器,使用目标类的类加载器 // - interfaces:目标类实现的接口列表 // - h:InvocationHandler实例 UserService proxy = (UserService) Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), handler ); // 4. 调用代理对象的方法 proxy.addUser("李四"); proxy.deleteUser("王五"); } }
运行结果:
〖日志〗调用方法:addUser,参数:[李四] 数据库新增用户:李四 〖日志〗方法执行完成,返回值:null 〖日志〗调用方法:deleteUser,参数:[王五] 数据库删除用户:王五 〖日志〗方法执行完成,返回值:null
2. CGLIB动态代理
CGLIB动态代理:通过第三方CGLIB库生成字节码,在内存中动态生成目标类的子类作为代理类,无需目标类实现接口,可以代理普通类-。
📌 注意:CGLIB无法代理被final修饰的类和方法(因为无法继承重写)。
CGLIB动态代理核心原理
通过ASM字节码操作框架,在运行时动态生成目标类的子类
在子类中重写所有非final的方法,将调用委托给
MethodInterceptor拦截器在拦截器的
intercept()方法中插入增强逻辑-
CGLIB动态代理代码示例
目标类(无需实现接口) :
public class UserService { public void addUser(String username) { System.out.println("数据库新增用户:" + username); } public void deleteUser(String username) { System.out.println("数据库删除用户:" + username); } }
实现MethodInterceptor:
import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method; public class LogMethodInterceptor implements MethodInterceptor { @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { // 前置增强 System.out.println("〖日志〗调用方法:" + method.getName() + ",参数:" + java.util.Arrays.toString(args)); // 调用父类(目标类)的原始方法 Object result = proxy.invokeSuper(obj, args); // 后置增强 System.out.println("〖日志〗方法执行完成,返回值:" + result); return result; } }
生成CGLIB代理对象:
import net.sf.cglib.proxy.Enhancer; public class CglibProxyDemo { public static void main(String[] args) { // 1. 创建Enhancer(CGLIB核心类) Enhancer enhancer = new Enhancer(); // 2. 设置父类(即目标类) enhancer.setSuperclass(UserService.class); // 3. 设置回调(MethodInterceptor) enhancer.setCallback(new LogMethodInterceptor()); // 4. 创建代理对象 UserService proxy = (UserService) enhancer.create(); // 5. 调用代理对象的方法 proxy.addUser("赵六"); proxy.deleteUser("钱七"); } }
四、概念关系与区别总结
静态代理 vs 动态代理
| 对比维度 | 静态代理 | 动态代理 |
|---|---|---|
| 生成时机 | 编译期手动编写,生成.class文件 | 运行期动态生成,无物理.class文件- |
| 代码量 | 每个目标类需要一个代理类,代码冗余 | 一个代理类可服务多个目标类,代码复用性强-3 |
| 灵活性 | 接口变更需同步修改代理类,扩展性差 | 无需手动修改,自动适配接口变更- |
| 性能 | 直接调用,性能略优 | 有反射/字节码操作开销,但JDK8+已大幅优化-39 |
| 适用场景 | 目标对象少、代理逻辑固定、对性能要求极高 | 目标对象多、需通用增强逻辑(日志/事务/权限)、框架底层 |
JDK动态代理 vs CGLIB动态代理
| 对比维度 | JDK动态代理 | CGLIB动态代理 |
|---|---|---|
| 代理方式 | 基于接口 | 基于继承(生成子类)-31 |
| 目标类要求 | 必须实现接口 | 无需接口,但不能是final类 |
| 底层技术 | 反射 + Proxy | ASM字节码增强-31 |
| 方法限制 | 只能代理接口中定义的方法 | 无法代理final和static方法-31 |
| 性能表现 | JDK8+优化后性能优于CGLIB | JDK8以下版本性能略优-39 |
| 依赖 | JDK原生,无需额外依赖 | 需要引入CGLIB库 |
🎯 一句话记忆:静态代理是“一对一”提前写好,动态代理是“一对多”运行时生成;JDK代理针对接口,CGLIB代理针对普通类。
五、底层原理探析
JDK动态代理底层原理
JDK动态代理的核心是反射机制 + 字节码动态生成。当调用Proxy.newProxyInstance()时,JVM会执行以下步骤-27:
动态生成字节码:根据传入的接口数组,在内存中动态生成一个实现这些接口的代理类(通常命名为
$Proxy0、$Proxy1等)编译加载:将生成的字节码通过类加载器加载到JVM中
实例化代理对象:通过反射获取代理类的构造方法,传入
InvocationHandler实例,创建代理对象
生成的代理类大致如下所示:
public final class $Proxy0 extends Proxy implements UserService { private static Method m1; public $Proxy0(InvocationHandler h) { super(h); // 将InvocationHandler存入父类Proxy中 } @Override public void addUser(String username) { try { // 调用InvocationHandler的invoke方法 super.h.invoke(this, m1, new Object[]{username}); } catch (RuntimeException | Error e) { throw e; } catch (Throwable t) { throw new UndeclaredThrowableException(t); } } }
底层支撑是Java的反射机制——Method.invoke()通过JVM内部的Klass元数据结构找到目标方法的入口地址并执行-27。
CGLIB动态代理底层原理
CGLIB的底层是ASM字节码操作框架。核心步骤-32:
生成子类:通过ASM在内存中动态生成目标类的子类
重写方法:在子类中重写所有非final的方法
拦截调用:被重写的方法内部调用
MethodInterceptor.intercept(),执行增强逻辑FastClass优化:CGLIB生成
FastClass类,通过方法索引直接调用目标方法,避免反射带来的性能损耗-34
六、高频面试题与参考答案
面试题1:什么是代理模式?静态代理和动态代理有什么区别?
参考答案:
代理模式是一种结构型设计模式,通过引入一个代理对象来控制对目标对象的访问,在不修改目标对象源码的前提下为其添加额外功能-38。
静态代理与动态代理的核心区别:
生成时机:静态代理在编译期手动编写代理类;动态代理在运行期动态生成代理类
代码量:静态代理需为每个目标类编写代理类,代码冗余;动态代理一个代理类可服务多个目标类
灵活性:静态代理灵活性差,接口变更需同步修改;动态代理灵活性强,自动适配变更-
踩分点:说出“编译期 vs 运行期”、“手动编写 vs 动态生成”、“一对一 vs 一对多”即可得高分。
面试题2:JDK动态代理和CGLIB动态代理有什么区别?Spring AOP如何选择?
参考答案:
| 区别维度 | JDK动态代理 | CGLIB动态代理 |
|---|---|---|
| 代理方式 | 基于接口 | 基于继承(生成子类) |
| 目标类要求 | 必须实现接口 | 无需接口,但不能是final类 |
| 底层技术 | 反射 + Proxy | ASM字节码增强 |
| 性能 | JDK8+优化后性能更好 | JDK8以下版本性能更优 |
Spring AOP的选择策略:默认情况下,如果目标类实现了接口,则使用JDK动态代理;如果目标类没有实现接口,则自动切换为CGLIB动态代理。也可通过<aop:config proxy-target-class="true">强制使用CGLIB-39。
踩分点:说清“接口 vs 继承”的核心差异,以及Spring的“自动选择”策略。
面试题3:动态代理在Spring框架中有哪些实际应用?
参考答案:
动态代理是Spring AOP的底层核心,主要应用场景包括:
声明式事务管理:在业务方法执行前自动开启事务,执行后提交事务,出现异常时回滚——开发者无需编写任何事务控制代码-39
统一日志记录:通过代理在方法执行前后记录入参、耗时、返回值,避免在每个业务方法中重复编写日志代码-39
权限校验拦截:在核心业务方法执行前校验用户权限,无权限则直接拒绝执行-39
性能监控:统计方法执行时间,为性能优化提供数据支撑
踩分点:举例说明即可,答出“事务管理”和“日志记录”两个典型场景基本过关。
面试题4:动态代理的底层实现原理是什么?
参考答案:
JDK动态代理:基于反射机制,通过
Proxy.newProxyInstance()在运行时动态生成实现指定接口的代理类字节码,所有方法调用都被转发给InvocationHandler.invoke(),通过Method.invoke()反射调用目标方法-27。CGLIB动态代理:基于ASM字节码增强,在运行时生成目标类的子类,重写所有非final方法,方法调用被
MethodInterceptor.intercept()拦截,通过MethodProxy.invokeSuper()调用父类原始方法-32。
踩分点:说出“反射 + Proxy生成”和“ASM + 继承子类”两个核心原理即可。
面试题5:为什么JDK动态代理只能代理接口?
参考答案:
JDK动态代理生成的代理类$Proxy0已经继承了java.lang.reflect.Proxy类。由于Java是单继承的,代理类无法再继承其他类,因此只能通过实现接口的方式来代理目标对象。如果要代理普通的类(没有实现接口),就需要使用CGLIB这种基于继承的方案-38。
踩分点:点出“单继承”和“已继承Proxy类”两个关键词。
七、结尾总结
本文从代码痛点出发,系统梳理了Java代理模式的完整知识体系:
✅ 静态代理:编译期确定,代码简单但冗余,适合目标对象少的场景
✅ JDK动态代理:运行期生成,基于接口+反射,Spring AOP的默认选择
✅ CGLIB动态代理:运行期生成,基于继承+字节码增强,可代理普通类
✅ 底层原理:JDK依赖反射,CGLIB依赖ASM字节码增强
✅ 面试要点:掌握三种代理的对比、底层原理、Spring中的应用场景
💡 学习建议:理论理解后,建议手动敲一遍本文的代码示例,亲自感受静态代理和动态代理的运行差异。只有代码跑起来了,面试时才不会“话到嘴边说不出”。
📚 下一期预告:深入Spring AOP源码,看代理模式如何实现“一键增强”,敬请期待!
本文由梦幻AI助手整理撰写,致力于为技术学习者提供高质量的知识服务。如果你觉得有帮助,欢迎点赞收藏,也欢迎在评论区留言讨论!