2026年4月8日 14:30 | 技术科普 + 原理讲解 + 代码示例 + 面试要点
代理模式是Java开发中最常用也最易混淆的核心设计模式之一。借助答题AI助手的检索能力,本文将系统梳理代理模式三种实现方式:静态代理、JDK动态代理与CGLIB动态代理,帮你彻底理清概念,解决“只会用、不懂原理、面试答不出”的学习痛点。

一、为什么需要代理模式?
假设业务中有日志记录、权限校验、事务管理等需求,最直接的方式是每个方法里都塞一段相同的代码:

public void doSomething() { System.out.println("开始日志..."); // 核心业务逻辑 System.out.println("结束日志..."); }
这种方法带来的问题:
代码冗余——同样的日志代码在多个方法中重复出现;
耦合度高——业务逻辑与非业务逻辑混在一起,修改日志规则就要改动业务代码;
扩展性差——如果要增加缓存或监控功能,需要修改所有相关方法;
维护困难——接口增加新方法时,所有实现类都得同步修改。
代理模式正是为解决这些问题而生的:在不修改原始类代码的前提下,通过代理对象控制对目标对象的访问,并在方法调用前后插入额外的增强逻辑。
二、核心概念A:静态代理
静态代理(Static Proxy) :在程序编译期就已经确定代理类和目标类的关系,代理类需要手动编写,且与目标类实现相同的接口-1。
生活化类比:周杰伦要开演唱会。他本人(目标对象)只管唱歌。经纪公司(代理对象)负责面谈、签合同、订机票、收钱等杂事。粉丝(客户端)通过经纪公司接触周杰伦,不需要直接和他本人打交道-30。
代码示例:
// 1. 抽象接口 public interface Star { void sing(); } // 2. 真实角色——周杰伦本人 public class RealStar implements Star { @Override public void sing() { System.out.println("周杰伦唱《七里香》~"); } } // 3. 代理角色——经纪人 public class ProxyStar implements Star { private Star star; // 持有真实角色的引用 public ProxyStar(Star star) { this.star = star; } @Override public void sing() { System.out.println("经纪人:面谈、签合同、订机票..."); star.sing(); // 调用真实角色 System.out.println("经纪人:收钱"); } } // 4. 客户端使用 public class Client { public static void main(String[] args) { Star realStar = new RealStar(); Star proxy = new ProxyStar(realStar); proxy.sing(); // 通过代理访问 } }
静态代理的核心问题:如果目标对象较多,就需要为每个目标对象写一个代理类,代码量成倍增长,维护成本极高-。这也正是动态代理出现的原因。
三、核心概念B:动态代理
动态代理(Dynamic Proxy) :在程序运行期间动态创建代理类,无需手动编写代理类代码。JVM在运行时根据指定的接口或目标类,动态生成代理对象的字节码并加载到内存中-37。
3.1 JDK动态代理
定义:JDK动态代理是Java原生提供的动态代理机制,位于java.lang.reflect包中,依赖Proxy类和InvocationHandler接口。要求目标类必须实现至少一个接口,因为生成的代理类会实现这些接口-60。
代码示例:
// 1. 定义接口(JDK代理强制要求) public interface UserService { void addUser(String name); } // 2. 目标类实现接口 public class UserServiceImpl implements UserService { @Override public void addUser(String name) { System.out.println("添加用户:" + name); } } // 3. 实现InvocationHandler接口 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; } } // 4. 创建代理对象并使用 public class Main { public static void main(String[] args) { UserService target = new UserServiceImpl(); InvocationHandler handler = new LogInvocationHandler(target); UserService proxy = (UserService) Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), handler ); proxy.addUser("张三"); } }
执行结果:
【日志】addUser方法开始执行 添加用户:张三 【日志】addUser方法执行结束
3.2 CGLIB动态代理
定义:CGLIB(Code Generation Library)是一个高性能的代码生成库,通过字节码技术动态生成目标类的子类来实现代理。不要求目标类实现接口,因此适用范围更广-22。
代码示例:
// 1. 目标类——不需要实现任何接口 public class UserService { public void addUser(String name) { System.out.println("添加用户:" + name); } } // 2. 实现MethodInterceptor接口 public class LogMethodInterceptor implements MethodInterceptor { @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("【日志】" + method.getName() + "方法开始执行"); Object result = proxy.invokeSuper(obj, args); // 调用父类方法 System.out.println("【日志】" + method.getName() + "方法执行结束"); return result; } } // 3. 创建代理对象 public class Main { public static void main(String[] args) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(UserService.class); enhancer.setCallback(new LogMethodInterceptor()); UserService proxy = (UserService) enhancer.create(); proxy.addUser("张三"); } }
四、概念关系与区别总结
| 对比维度 | 静态代理 | JDK动态代理 | CGLIB动态代理 |
|---|---|---|---|
| 生成时机 | 编译期 | 运行期 | 运行期 |
| 是否需要接口 | 需要 | 需要(强制) | 不需要 |
| 实现原理 | 手动编码 | 反射 + 字节码生成 | ASM字节码生成子类 |
| 能否代理final类/方法 | 可代理 | 不涉及 | 不能 |
| 代码量 | 每个类一个代理类 | 一个Handler复用 | 一个Interceptor复用 |
| 性能 | 最高 | 中等(反射开销) | 较高(直接调用) |
| 适用场景 | 少量固定代理 | 接口代理、Spring AOP | 无接口代理、Spring AOP |
一句话记忆:静态代理是“手写的替身”,JDK动态代理是“反射生成的接口替身”,CGLIB是“字节码生成的子类替身”。
五、底层原理 / 技术支撑
JDK动态代理的底层支撑——反射 + 字节码生成:
当调用Proxy.newProxyInstance()时,JVM执行三个步骤:先在内存中根据传入的接口拼装出代理类的字节码(格式固定),该代理类实现所有指定接口,每个方法的实现都调用InvocationHandler.invoke();然后将字节码加载进JVM生成Class<?>对象;最后通过反射调用代理类的构造函数生成实例-9。
CGLIB动态代理的底层支撑——ASM字节码操作框架:
CGLIB底层使用ASM(一个轻量级的字节码处理框架)来操作字节码。首先创建Enhancer对象并设置目标类为父类;然后设置MethodInterceptor回调;调用create()方法时,CGLIB使用ASM动态生成目标类的子类,该子类覆盖所有非final方法,将调用委托给拦截器-22。
六、高频面试题与参考答案
1. 什么是代理模式?它的核心作用是什么?
参考答案:代理模式是一种结构型设计模式,通过创建一个代理对象来控制对目标对象的访问。核心作用有两个:控制访问(在不直接访问目标对象的情况下间接操作)和增强功能(在目标方法执行前后添加额外逻辑,如日志、事务、权限校验等)-37。
2. 静态代理和动态代理有什么区别?
参考答案:时机不同——静态代理在编译期确定代理关系,代理类在代码中已写好;动态代理在运行期动态生成代理类。代码量不同——静态代理需为每个目标类写一个代理类,代码冗余;动态代理只需编写一次调用处理器或拦截器,即可为多个目标类生成代理对象。灵活性不同——接口新增方法时,静态代理的代理类和目标类都需修改,违背开闭原则;动态代理无需修改--1。
3. 为什么JDK动态代理只能代理接口?
参考答案:因为JDK动态代理生成的代理类已经继承了Proxy类。Java是单继承语言,一个类只能继承一个父类,无法再继承目标类。所以JDK代理只能通过实现接口的方式来定义代理对象的行为。生成的代理类会实现目标类所实现的接口,并在接口方法中调用InvocationHandler.invoke()-60。
4. JDK动态代理和CGLIB动态代理各自有什么优缺点?
参考答案:JDK动态代理——优点是不依赖第三方库,由JDK原生支持;缺点是要求目标类必须实现接口,无法代理无接口的类。CGLIB动态代理——优点是不需要目标类实现接口,可以代理任意普通类;缺点是依赖CGLIB库,无法代理final类或final方法(因为基于继承生成子类),且生成代理类的过程比JDK稍复杂-22-54。
5. Spring AOP默认使用哪种代理方式?什么情况下会切换?
参考答案:Spring AOP默认优先使用JDK动态代理。如果目标类实现了至少一个接口,Spring默认使用JDK代理;如果目标类没有实现任何接口,Spring会自动切换为CGLIB代理。也可以通过配置强制使用CGLIB代理-22。
七、结尾总结
回顾全文核心知识点:
| 类型 | 核心特点 | 一句话记忆 |
|---|---|---|
| 静态代理 | 编译期生成,手动编码 | 手写的替身,每个目标一个代理类 |
| JDK动态代理 | 运行期生成,需要接口,基于反射 | 反射生成的接口替身 |
| CGLIB动态代理 | 运行期生成,无需接口,基于字节码 | ASM生成的子类替身 |
重点强调:实际开发中动态代理使用频率远超静态代理,但静态代理是理解代理模式的敲门砖,不可跳过。面试中关于JDK代理为什么只能代理接口的问题,核心原因是“代理类已继承Proxy类,无法再继承目标类”。
下期预告:Spring AOP底层源码解析——看JDK与CGLIB如何被Spring整合运用,并附源码级流程图。