更新时间:北京时间2026年4月10日
在日常开发中,你是否经常需要在核心业务方法前后加入日志打印、性能监控或权限校验?如果直接写在方法里,代码会迅速膨胀;如果使用Java代理模式这一经典设计思想,你就能在完全不修改原有代码的前提下优雅实现功能增强。本文将由AI信息助手带你从零理解代理模式的核心概念、静态与动态代理的区别,并通过代码示例和面试题,帮助你建立完整知识链路。

一、痛点切入:为什么我们需要代理模式
假设你有一个支付服务类,现在要为它添加方法耗时监控和操作日志。最直接的做法是在每个方法里塞进这些逻辑:

public class PaymentService { public void pay() { // 监控、限流、审计...(占比80%) long start = System.nanoTime(); System.out.println("〖日志〗开始执行支付..."); realPay(); // 核心业务(占比20%) System.out.println("〖日志〗支付完成"); System.out.println("耗时:" + (System.nanoTime() - start) + " ns"); // 事务提交、日志回收...(占比80%) } private void realPay() { / 核心支付逻辑 / } }
这种方式存在三大问题:
违背开闭原则:每次修改增强逻辑,都要改动业务代码,容易引入Bug-50
代码重复度高:多个Service类都需要日志、监控时,每个类都要重复编写相同逻辑
类膨胀严重:如果有100个Service接口,为了实现日志增强,你需要手写100个代理类-50
代理模式正是为解决这一问题而生的设计模式——它通过引入代理对象作为中间层,在不修改目标类代码的前提下,为核心业务添加增强功能-61。
二、静态代理:编译期确定的“专属经纪人”
2.1 标准定义
静态代理(Static Proxy) 是指在编译期就已确定代理类与被代理类关系的代理实现方式。代理类和目标类需要实现相同的接口,代理类内部持有目标对象的引用,在接口方法中调用目标对象的相应方法-1。
生活化类比:静态代理就像明星的 “专属经纪人” ,一个经纪人只服务一位明星,一对一绑定。客户找明星谈合作时,必须先通过经纪人——经纪人会处理合同、排期等事务,最后才把核心业务(演出)交给明星本人-2。
2.2 代码示例
// 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. 使用 UserService service = new UserServiceProxy(new UserServiceImpl()); service.addUser("张三");
2.3 静态代理的致命缺陷
静态代理实现简单,但存在三大“死穴”-50-:
| 缺陷 | 说明 |
|---|---|
| 类膨胀 | 每个目标类都需要一个专属代理类,100个Service就要写100个代理类 |
| 维护噩梦 | 接口新增方法,所有实现类和代理类都要同步修改 |
| 重复劳动 | 日志、监控等增强逻辑在每个代理类中重复编写 |
静态代理就像为每个工人配备专属监工——项目规模扩大后,监工比工人还多。这正是动态代理登场的直接原因。
三、JDK动态代理:运行时生成的“中介公司”
3.1 标准定义
JDK动态代理(JDK Dynamic Proxy) 是Java原生支持的动态代理技术,它利用反射机制在运行时动态生成代理类的字节码。代理类会继承java.lang.reflect.Proxy类并实现目标对象的所有接口,所有方法调用都会被转发到InvocationHandler的invoke()方法中进行处理-16-。
生活化类比:JDK动态代理就像一家 “正规中介公司” 。明星不需要提前安排专属经纪人,中介公司持有明星的“营业执照”(接口),能根据客户需求动态匹配,统一处理合同、排期等事务-32。
3.2 核心组件
JDK动态代理依赖两个关键类-:
InvocationHandler(调用处理器) :定义了代理对象被调用时的处理逻辑,核心方法是invoke()Proxy(代理类) :提供newProxyInstance()静态方法,用于动态生成代理对象
3.3 代码示例
// 业务接口(JDK代理强制要求) public interface OrderService { void createOrder(String orderId); void payOrder(String orderId); } // 目标类:实现接口,专注核心业务 public class OrderServiceImpl implements OrderService { @Override public void createOrder(String orderId) { System.out.println("创建订单:" + orderId); } @Override public void payOrder(String orderId) { System.out.println("支付订单:" + orderId); } } // 动态代理的调用处理器 import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; 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() + "] 开始执行"); long start = System.nanoTime(); // 通过反射调用目标对象的真实方法 Object result = method.invoke(target, args); // 后置增强:性能统计 System.out.println("〖日志〗方法执行耗时:" + (System.nanoTime() - start) + " ns"); return result; } } // 使用动态代理 public class Main { public static void main(String[] args) { OrderService target = new OrderServiceImpl(); OrderService proxy = (OrderService) Proxy.newProxyInstance( target.getClass().getClassLoader(), // 类加载器 target.getClass().getInterfaces(), // 要实现的接口数组 new LogInvocationHandler(target) // 调用处理器 ); // 通过代理对象调用方法 → 自动触发invoke() proxy.createOrder("ORD-001"); proxy.payOrder("ORD-001"); } }
关键执行流程:
Proxy.newProxyInstance()在运行时动态生成代理类(类名如$Proxy0)的字节码并加载到JVM调用代理对象的方法(如
createOrder())时,会被内部统一转发给InvocationHandler.invoke()invoke()中可以通过method.invoke(target, args)反射调用目标对象的真实方法-14
3.4 为什么只能代理接口?
JDK动态代理只能代理实现了接口的类。原因在于:Proxy.newProxyInstance()生成的代理类会继承Proxy类并实现指定的接口。由于Java不支持多重继承,代理类无法再继承其他类,因此目标类必须通过接口来定义行为-14。
四、CGLIB动态代理:基于继承的“克隆人工厂”
4.1 标准定义
CGLIB(Code Generation Library) 是一个基于Java的开源代码生成库,它通过动态生成目标类的子类来实现代理。CGLIB底层依赖ASM字节码框架,能够在运行时为目标类生成一个子类,并重写其中的非final方法,在方法调用时进行拦截-24-。
生活化类比:CGLIB就像一家 “高科技克隆人工厂” 。不管明星有没有营业执照,工厂直接提取明星的“DNA”(类结构),瞬间克隆出一个长得一模一样的“子类”。这个克隆人继承了明星的所有能力,并在做事前后自动加上日志、监控-32。
4.2 核心组件
Enhancer:CGLIB的核心入口类,用于配置和生成代理对象MethodInterceptor:方法拦截器接口,定义了拦截逻辑
4.3 代码示例
// 目标类:无需实现任何接口(但不能是final类) public class PaymentService { public void pay(double amount) { System.out.println("执行支付:" + amount + "元"); } } // 方法拦截器 import net.sf.cglib.proxy.Enhancer; 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() + "] 开始执行"); long start = System.nanoTime(); // 调用父类的原始方法(注意:是invokeSuper,不是method.invoke) Object result = proxy.invokeSuper(obj, args); // 后置增强 System.out.println("〖日志〗执行耗时:" + (System.nanoTime() - start) + " ns"); return result; } } // 使用CGLIB代理 public class Main { public static void main(String[] args) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(PaymentService.class); // 设置目标类作为父类 enhancer.setCallback(new LogMethodInterceptor()); // 设置回调拦截器 PaymentService proxy = (PaymentService) enhancer.create(); // 生成代理对象 proxy.pay(100.0); } }
关键执行流程:
Enhancer.create()使用ASM在运行时动态生成目标类的子类(类名如PaymentService$$EnhancerByCGLIB$$xxx)该子类重写所有非final方法,在方法体中调用
MethodInterceptor.intercept()intercept()中通过MethodProxy.invokeSuper()调用父类的原始方法-22
五、概念关系与核心区别总结
5.1 逻辑关系一句话概括
代理模式是“思想”,静态代理是“手工实现”,动态代理是“自动化实现”;JDK代理是“接口驱动的动态代理”,CGLIB是“继承驱动的动态代理” -61
5.2 三种代理方式对比表
| 对比维度 | 静态代理 | JDK动态代理 | CGLIB动态代理 |
|---|---|---|---|
| 代理类生成时机 | 编译期 | 运行时 | 运行时 |
| 依赖条件 | 目标类需实现接口 | 目标类必须实现接口 | 目标类不能是final类 |
| 核心原理 | 手动编写代理类 | 反射 + Proxy | ASM字节码生成子类 |
| 代码侵入 | 需为每个类写代理 | 无侵入 | 无侵入 |
| 代理类数量 | N个目标类 → N个代理类 | 1个Handler → N个代理对象 | 1个Interceptor → N个代理对象 |
| 方法调用性能 | 直接调用(最快) | 反射调用(略慢) | 字节码调用(快) |
| 代理类创建速度 | 编译时完成 | 快(无需生成字节码) | 慢(需ASM生成字节码) |
| 能否代理final方法 | 否 | N/A(接口无final方法) | 否,且final类完全无法代理 |
| 能否代理无接口类 | 是(需实现同一接口) | 否 | 是 |
| 额外依赖 | 无 | 无 | 需cglib依赖(Spring已内置) |
5.3 性能差异说明
JDK动态代理在代理对象创建时较快(无需生成字节码),但方法调用时通过反射执行,有一定性能开销;CGLIB在代理对象创建时较慢(需ASM生成字节码并加载),但方法调用时直接操作字节码,执行效率更高-31。不过在JDK 8及以上版本中,两种方式的性能差距已显著缩小-。
六、底层原理与技术支撑
6.1 JDK动态代理的底层依赖
JDK动态代理的核心底层依赖是 Java反射机制(Reflection) -:
Proxy.newProxyInstance()通过ProxyGenerator.generateProxyClass()在运行时拼装代理类的字节码生成的代理类(如
$Proxy0)会继承Proxy类,并实现指定的接口代理类中的每个方法体都是一个固定模板:
super.h.invoke(this, mXX, args)——所有调用统一转发到InvocationHandler.invoke()method.invoke(target, args)则利用反射在运行时动态调用目标对象的真实方法-15
动态代理的“动态”本质:代理类不是在编译期写死的,而是由JVM在运行时根据接口定义动态生成字节码并加载,因此一个InvocationHandler可以为任意多个目标类提供服务。
6.2 CGLIB的底层原理
CGLIB底层依赖 ASM字节码操作框架,直接操作JVM的字节码指令-24:
通过ASM为目标类动态生成一个子类,并重写所有非final方法
在重写的方法体中,插入对
MethodInterceptor.intercept()的调用逻辑CGLIB通过 FastClass机制 使用方法索引替代反射调用,进一步提升运行时性能-24
生成代理类时会包含三份字节码文件:代理类本身、FastClass索引类等
提示:底层原理的深入理解是面试中的加分项,本文作为入门教程不展开源码细节,后续进阶内容会深入分析字节码生成流程和JVM类加载机制。
七、高频面试题与参考答案
面试题1:请简述静态代理和动态代理的区别?
参考答案:
静态代理:代理类在编译期就已确定,需要手动编写,代理类与目标类一一对应。优点是实现简单,缺点是类膨胀严重、维护成本高-
动态代理:代理类在运行期由JVM动态生成,无需手动编写代理类代码。一个动态代理处理器可以为任意多个目标类提供代理服务-49
一句话概括:静态代理是“手工打造”,动态代理是“3D打印”
面试题2:JDK动态代理和CGLIB有什么区别?各自适用于什么场景?
参考答案:
| 维度 | JDK动态代理 | CGLIB |
|---|---|---|
| 原理 | 基于接口,代理类实现目标接口 | 基于继承,代理类是目标类的子类 |
| 依赖 | 目标类必须实现接口 | 目标类不能是final类 |
| 创建速度 | 快 | 慢(需ASM生成字节码) |
| 调用性能 | 反射调用,略慢 | 字节码调用,较快 |
适用场景:
目标类已实现接口 → 优先用JDK代理(更简洁,符合面向接口编程)
目标类无接口或需要代理内部方法 → 用CGLIB(功能更强大)-31-
面试题3:Spring AOP默认使用哪种动态代理?为什么?
参考答案:
Spring Framework(Spring 3.2+) :默认优先使用JDK动态代理。当目标对象实现了接口时用JDK代理,没有实现接口时自动切换为CGLIB-
Spring Boot 2.x+ :将默认值改为了CGLIB-
选择原因:JDK代理是Java原生支持、无额外依赖;CGLIB可以代理无接口的类,覆盖更广的使用场景
面试题4:JDK动态代理为什么只能代理有接口的类?
参考答案:
因为Proxy.newProxyInstance()生成的代理类必须继承java.lang.reflect.Proxy类。由于Java是单继承的,代理类无法再继承其他类。因此目标类必须通过接口来定义行为,代理类通过实现相同接口来替代目标对象-14。如果传入一个没有实现接口的类,会直接抛出IllegalArgumentException。
面试题5:CGLIB能代理final类或final方法吗?为什么?
参考答案:
不能。 CGLIB基于继承实现代理——它会动态生成目标类的子类,通过重写非final方法来添加增强逻辑。
如果目标类是final类,无法被继承,会抛出
IllegalArgumentException: Cannot subclass final class如果方法是final方法,子类无法重写,因此无法被拦截和增强-22-31
八、结尾总结
本文围绕Java代理模式这一核心知识点,按“问题驱动 → 概念讲解 → 代码演示 → 原理分析 → 面试考点”的递进逻辑,系统梳理了:
| 知识点 | 核心要点 |
|---|---|
| 静态代理 | 编译期确定,一对一的“专属经纪人”,简单但类膨胀严重 |
| JDK动态代理 | 运行时生成,依赖接口,基于反射,1个Handler服务N个类 |
| CGLIB动态代理 | 运行时生成,依赖继承,基于ASM字节码,无法代理final类/方法 |
| 面试考点 | 三种方式的区别、适用场景、Spring AOP的默认策略 |
一句话考点记忆:
静态代理 = 手写代理类,一对一的“专属经纪人”
JDK代理 = 反射+接口,一对多的“中介公司”
CGLIB代理 = ASM+继承,无接口也能干的“克隆人工厂”
下一篇预告:本文重点讲解了代理模式的概念、区别和代码示例。下一篇文章将深入代理模式的底层实现——从ProxyGenerator的字节码生成原理到CGLIB的ASM源码剖析,带你彻底搞懂“动态”二字背后的JVM机制。欢迎持续关注AI信息助手的系列教程!