在Java技术体系中,代理模式(Proxy Pattern) 是面试高频考点,也是Spring AOP(面向切面编程,Aspect-Oriented Programming)的底层实现基石,属于每位Java开发者必须掌握的核心知识点-。很多初学者在接触代理模式时,往往陷入“看得懂定义、写不出代码、搞不清区别”的困境:静态代理和动态代理到底差在哪?JDK动态代理为什么非要接口?CGLIB又是怎么一回事?本文将从传统实现方式的痛点切入,系统讲解静态代理与动态代理的原理、区别与代码实现,同时梳理高频面试考点,帮助你建立完整的知识链路。
一、痛点切入:为什么需要代理模式?

传统直接调用的问题
假设你有一个用户服务,需要在执行保存操作前后添加“开启事务”和“提交事务”的逻辑。最直接的方式是在业务代码中硬编码:

public class UserService { public void save() { System.out.println("开启事务"); System.out.println("保存用户数据"); System.out.println("提交事务"); } }
这种方式的问题显而易见:事务管理与业务逻辑高度耦合,每个需要事务的方法都要重复编写相同的代码,代码冗余度极高,维护成本巨大。
静态代理方案
我们可以创建一个代理类来解决这个问题:
// 1. 统一接口 public interface IUserService { void save(); } // 2. 真实对象(只关注核心业务) public class UserService implements IUserService { @Override public void save() { System.out.println("保存用户数据"); } } // 3. 静态代理对象(负责额外逻辑) public class UserServiceProxy implements IUserService { private IUserService target; public UserServiceProxy(IUserService target) { this.target = target; } @Override public void save() { System.out.println("开启事务"); // 前置增强 target.save(); // 调用真实业务 System.out.println("提交事务"); // 后置增强 } }
测试代码:
public class Test { public static void main(String[] args) { IUserService target = new UserService(); IUserService proxy = new UserServiceProxy(target); proxy.save(); } } // 输出:开启事务 → 保存用户数据 → 提交事务
静态代理的致命缺陷
静态代理虽然实现了业务逻辑与横切关注点的分离,但存在两个严重问题--5:
① 代码冗余:每个需要代理的类都要单独编写一个代理类,当系统中存在几十上百个服务类时,代理类的数量会急剧膨胀。
② 维护困难:如果接口中新增了一个方法,所有真实类和代理类都要同步修改,违背开闭原则。
静态代理的核心特征是编译时确定——代理类和被代理类的关系在编译期就已经固定,代理类是一个实际的 .class 文件-5。这种固化的关系在业务场景多变的今天,显然不够灵活。
二、动态代理:运行时生成的解决方案
什么是动态代理?
动态代理(Dynamic Proxy)是指代理对象在运行时动态生成的代理方式,编译完成后没有实际的 .class 文件,而是在运行时动态生成类字节码并加载到JVM中-5。一个动态代理类可以为任意多个真实类提供代理服务,完美解决了静态代理的扩展性问题-45。
生活化类比
可以把静态代理想象成每个明星都有一个固定的经纪人(一对一),而动态代理是一个专业经纪公司,可以根据需要为任意明星临时安排经纪服务(一对多)。
三、JDK动态代理(基于接口)
核心定义
JDK动态代理是Java标准库提供的动态代理机制,位于 java.lang.reflect 包下。它基于反射(Reflection) 实现,通过 Proxy 类动态创建实现了指定接口的代理实例-48。
两大核心组件
| 组件 | 作用 |
|---|---|
java.lang.reflect.Proxy | 提供 newProxyInstance() 方法动态生成代理对象 |
java.lang.reflect.InvocationHandler | 定义 invoke() 方法,所有代理方法调用都会转发至此 |
完整代码示例
// 1. 定义接口(必须!JDK动态代理要求被代理类实现至少一个接口) public interface Hello { void sayHello(); } // 2. 真实对象 public class HelloImpl implements Hello { @Override public void sayHello() { System.out.println("Hello, World!"); } } // 3. 自定义InvocationHandler import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; public class MyInvocationHandler implements InvocationHandler { private Object target; // 持有真实对象的引用 public MyInvocationHandler(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; } } // 4. 创建代理对象并调用 import java.lang.reflect.Proxy; public class Main { public static void main(String[] args) { Hello target = new HelloImpl(); MyInvocationHandler handler = new MyInvocationHandler(target); Hello proxy = (Hello) Proxy.newProxyInstance( target.getClass().getClassLoader(), // 类加载器 target.getClass().getInterfaces(), // 目标对象实现的接口列表 handler // 事件处理器 ); proxy.sayHello(); // 代理方法调用会转发到handler.invoke() } }
执行流程
调用
Proxy.newProxyInstance()→ JVM在内存中动态生成代理类字节码代理类实现所有指定接口,每个方法的实现内部都会调用
InvocationHandler.invoke()调用代理对象的方法 → 触发
invoke()→ 在invoke()中执行增强逻辑 → 通过反射调用真实对象方法-16
核心限制
JDK动态代理只能代理实现了接口的类。如果目标类没有实现任何接口,则无法使用JDK动态代理-48-17。
四、CGLIB动态代理(基于继承)
为什么需要CGLIB?
JDK动态代理要求目标类必须实现接口,但在实际开发中,很多类并没有实现接口(例如第三方库中的类)。CGLIB(Code Generation Library)正是为了解决这一问题而诞生的-。
核心定义
CGLIB是一个强大的高性能代码生成库,通过ASM字节码处理框架动态生成目标类的子类来实现代理。它不要求目标类实现任何接口,因此适用于那些没有实现接口的类-28。
完整代码示例
需要先引入CGLIB依赖(Maven):
<dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.3.0</version> </dependency>
// 1. 真实对象(不需要实现任何接口!) public class UserService { public void save() { System.out.println("保存用户数据"); } } // 2. 自定义MethodInterceptor import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method; public class MyMethodInterceptor implements MethodInterceptor { @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("========== 前置增强 =========="); Object result = proxy.invokeSuper(obj, args); // 调用父类方法 System.out.println("========== 后置增强 =========="); return result; } } // 3. 使用Enhancer创建代理对象 import net.sf.cglib.proxy.Enhancer; public class Main { public static void main(String[] args) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(UserService.class); // 设置父类(目标类) enhancer.setCallback(new MyMethodInterceptor()); // 设置拦截器 UserService proxy = (UserService) enhancer.create(); // 创建代理对象 proxy.save(); } }
CGLIB的工作原理
CGLIB通过ASM字节码技术,在运行时动态生成目标类的子类,并在子类中重写需要代理的方法-。当调用代理对象的方法时,会先被 MethodInterceptor.intercept() 拦截,在拦截方法中可以添加增强逻辑,然后通过 proxy.invokeSuper() 调用父类(原目标类)的方法-26。
核心限制
由于CGLIB是通过继承方式创建子类来实现代理,因此无法代理 final 类和 final 方法,因为 final 类不能被继承,final 方法不能被重写-26。
五、JDK动态代理 vs CGLIB:全面对比
| 对比维度 | JDK动态代理 | CGLIB动态代理 |
|---|---|---|
| 代理方式 | 基于接口 | 基于继承(生成子类) |
| 目标类要求 | 必须实现至少一个接口 | 无需接口,但类和方法不能是final |
| 底层技术 | 反射 + Proxy | ASM字节码增强 |
| 性能特征 | JDK 8前较慢;JDK 9+优化后与CGLIB差距缩小 | 生成代理类开销大,方法调用性能较高 |
| Spring AOP适用场景 | 代理实现了接口的类 | 代理没有实现接口的类 |
| 生成的代理类 | $Proxy0 | 类名$$EnhancerByCGLIB$$xxx |
一句话总结:JDK动态代理是 “面向接口” 的代理方案,CGLIB动态代理是 “面向类” 的代理方案-26。
六、底层原理支撑
JDK动态代理的底层机制
JDK动态代理的底层依赖于Java的反射机制。当调用 Proxy.newProxyInstance() 时,JVM会执行以下三个步骤-16-:
生成字节码:根据传入的接口列表,在内存中动态拼装出一个实现了这些接口的Java类的字节码,代理类会继承
Proxy类类加载:使用指定的类加载器将生成的字节码加载到JVM中,生成代理类的
Class对象实例创建:通过反射调用代理类的构造函数,传入
InvocationHandler实例,生成代理对象
CGLIB的底层机制
CGLIB底层依赖ASM字节码处理框架,它直接操作Java字节码,在运行时动态生成目标类的子类。相比于JDK动态代理的反射调用,CGLIB在方法调用时减少了反射开销,因此方法执行性能更高-28。
这两个底层知识点是面试中的加分项,也是后续学习Spring AOP源码的基础。
七、实战应用:Spring AOP中的动态代理
Spring AOP(面向切面编程)的底层核心就是动态代理技术。Spring根据目标类是否实现接口,自动选择合适的代理方式-:
如果目标类实现了接口,Spring默认使用JDK动态代理
如果目标类没有实现接口,Spring自动切换到CGLIB动态代理
从Spring Boot 2.x开始,spring.aop.proxy-target-class 的默认值被设置为 true,即默认优先使用CGLIB代理-。
这也是为什么代理模式在面试中如此高频的原因——它直接关系到你对Spring核心原理的理解深度。
八、高频面试题与参考答案
Q1:静态代理和动态代理的区别是什么?
参考答案:
生成时机不同:静态代理在编译期确定代理关系,代理类是实际的
.class文件;动态代理在运行期动态生成代理类字节码,编译后没有实际的.class文件-5灵活性不同:静态代理每个真实类需要一个代理类,代码冗余;动态代理一个代理类可以为任意多个真实类提供服务
实现复杂度不同:静态代理实现简单;动态代理需要掌握反射和
InvocationHandler接口性能不同:静态代理直接调用,性能略高;动态代理涉及反射调用,有一定性能开销-6
Q2:为什么JDK动态代理必须依赖接口?
参考答案:因为JDK动态代理生成的代理类会继承 Proxy 类,而Java是单继承机制,代理类无法再继承其他类,只能通过实现接口的方式来扩展目标类的方法。生成的代理类会实现所有指定的接口,并将方法调用全部转发给 InvocationHandler.invoke()-48-。
Q3:JDK动态代理和CGLIB动态代理有什么区别?各自适用什么场景?
参考答案:JDK动态代理基于接口,要求目标类必须实现接口,底层使用反射;CGLIB基于继承,通过ASM生成子类,不要求接口但无法代理 final 类和方法-26。适用场景上,JDK适合有接口定义的类,CGLIB适合无接口的类。在Spring AOP中,框架会根据目标类是否实现接口自动选择代理方式。
Q4:CGLIB为什么不能代理final类?
参考答案:CGLIB通过生成目标类的子类来实现代理,而Java中的 final 类不能被继承,final 方法不能被重写,因此CGLIB无法对 final 类或 final 方法进行代理-。
Q5:Spring AOP默认使用哪种动态代理?
参考答案:Spring Framework默认使用JDK动态代理,但从Spring 3.2开始内置了CGLIB。如果目标类没有实现接口,会自动切换到CGLIB。Spring Boot 2.x将 spring.aop.proxy-target-class 默认值改为 true,即默认使用CGLIB代理-。
九、总结回顾
本文围绕Java代理模式,从痛点切入,系统讲解了:
| 核心知识点 | 要点回顾 |
|---|---|
| 静态代理 | 编译时生成,每个目标类需要一个代理类,代码冗余、维护困难 |
| JDK动态代理 | 运行时生成,基于接口+反射,必须要求目标类实现接口 |
| CGLIB动态代理 | 运行时生成,基于继承+ASM字节码,不要求接口,但无法代理final类 |
| 两者核心关系 | 静态代理是思想,动态代理是更灵活的实现方式;JDK和CGLIB是动态代理的两种具体实现 |
易错点提醒:
不要混淆静态代理和动态代理的生成时机(编译期 vs 运行期)
JDK动态代理必须依赖接口,这是面试中经常被问到的“为什么”
CGLIB不能代理
final方法和final类,这是使用时的硬性限制代理模式和装饰器模式容易混淆:代理模式重在 “控制访问” ,装饰器模式重在 “增强功能” -
动态代理是连接Java基础与框架源码的重要桥梁。掌握了这些知识,你不仅能应对高频面试题,更为深入理解Spring AOP、MyBatis Mapper代理、RPC框架等底层机制打下了坚实基础。
下一篇预告:我们将深入Spring AOP源码,剖析 DefaultAopProxyFactory 如何根据目标类特征自动选择代理方式,以及代理调用链的完整执行流程,敬请期待!