关键词:代理模式、静态代理、JDK动态代理、CGLIB、Spring AOP
一、基础信息配置

文章标题:会计AI助手|Java代理模式深度解析:静态代理→JDK→CGLIB全掌握
发布时间:2026年4月8日

目标读者:技术入门/进阶学习者、在校学生、面试备考者、Java开发工程师
文章定位:技术科普 + 原理讲解 + 代码示例 + 面试要点,兼顾易懂性与实用性
写作风格:条理清晰、由浅入深、语言通俗、重点突出,少晦涩理论,多对比与示例
核心目标:让读者理解概念、理清逻辑、看懂示例、记住考点,建立完整知识链路
开篇引入
在Java面试中,代理模式几乎是必考知识点。但很多学习者存在一个共同痛点:会用Spring AOP的@Transactional注解,却说不清JDK动态代理和CGLIB有什么区别;能在项目中写静态代理,却被问到“为什么Spring默认用JDK代理”时哑口无言。本文从痛点切入 → 概念讲解 → 代码示例 → 底层原理 → 面试考点,由浅入深带你吃透代理模式。会计AI助手提示:本文内容同样适用于理解AOP底层机制、MyBatis Mapper代理等框架设计思想,文末附高频面试题及答案,建议收藏反复阅读。
二、痛点切入:为什么需要代理模式?
先看一段“裸写”的代码:假设要为用户服务的方法添加日志记录。
public class UserServiceImpl { public void addUser(String username) { System.out.println("=== 日志开始 ==="); System.out.println("调用方法:addUser,参数:" + username); System.out.println("〖核心业务〗添加用户:" + username); System.out.println("=== 日志结束 ==="); } }
问题很明显:日志代码与业务代码耦合在一起。如果还要添加权限校验、事务管理,每个方法都会变得臃肿不堪。
传统方式的四大痛点
代码重复:相同的增强逻辑(日志、事务)要在每个方法中重复编写
耦合度高:业务代码与非功能性代码混杂,修改日志格式要改所有方法
维护困难:新增一个增强功能,需要修改所有相关方法
违反开闭原则:对扩展不开放,对修改却开放
代理模式的解决方案
代理模式的核心思想是:通过引入一个“代理对象”作为中间层,在不修改目标对象源代码的前提下,对方法调用进行控制和增强。就像租房找中介——你不需要直接跟房东打交道,中介帮你完成所有事情,还能提供额外服务。
三、核心概念讲解:代理模式
标准定义:代理模式(Proxy Pattern)是一种结构型设计模式,通过引入代理对象来控制对目标对象的访问,并在访问前后添加额外功能,同时不改变目标对象的代码。-1
三个核心角色
| 角色 | 英文 | 作用 | 生活类比 |
|---|---|---|---|
| 抽象主题 | Subject | 定义代理和目标对象的共同接口 | “租房”这个行为规范 |
| 真实主题 | RealSubject | 执行实际业务逻辑的对象 | 房东(真正出租房子的人) |
| 代理 | Proxy | 控制对真实主题的访问,负责功能增强 | 中介(帮你筛选房源、签合同) |
代理模式的分类
根据代理类的生成时机,代理模式分为两大类:-3
静态代理:代理类在编译期就已确定,由程序员手动编写
动态代理:代理类在运行期动态生成,无需手动编写代理类代码
四、关联概念讲解:静态代理
定义:静态代理是在编译期就已经确定代理类和被代理类的关系,代理类需要手动编写,与被代理类实现相同的接口。-2
代码示例:给用户服务添加日志
① 定义抽象主题(接口)
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); } }
③ 代理主题(代理类)—— 关键代码
public class UserServiceProxy implements UserService { private UserService target; // 持有真实主题引用 public UserServiceProxy(UserService target) { this.target = target; } @Override public void addUser(String username) { System.out.println("===== 日志开始 ====="); // 前置增强 target.addUser(username); // 调用核心业务 System.out.println("===== 日志结束 ====="); // 后置增强 } @Override public void deleteUser(String username) { System.out.println("===== 日志开始 ====="); target.deleteUser(username); System.out.println("===== 日志结束 ====="); } }
④ 客户端调用
UserService target = new UserServiceImpl(); UserService proxy = new UserServiceProxy(target); proxy.addUser("张三");
静态代理的致命缺陷
代码冗余:每个被代理类都需要一个对应的代理类,如果有10个业务类就需要写10个代理类-39
扩展性差:如果接口新增方法,代理类也必须同步修改
维护成本高:增强逻辑(如日志)要在每个代理类中重复编写,修改一处需要改所有代理类
五、概念关系与区别总结
代理模式的核心脉络可以用一句话概括:静态代理是“手动挡”,动态代理是“自动挡”——静态代理在编译期手动写死,动态代理在运行期自动生成。
| 对比维度 | 静态代理 | 动态代理 |
|---|---|---|
| 代理类生成时机 | 编译期 | 运行期 |
| 是否需要手动编写 | ✅ 需要 | ❌ 不需要 |
| 代码冗余程度 | 高(每类一个代理) | 低(一个代理类可服务多个目标) |
| 灵活性 | 低 | 高 |
| 性能 | 略高(直接调用) | 略低(反射/字节码开销) |
| 维护成本 | 高 | 低 |
六、代码/流程示例演示:JDK动态代理
JDK动态代理是Java标准库提供的动态代理机制,依赖 java.lang.reflect.Proxy 类和 InvocationHandler 接口。-10
完整代码示例
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; // ① 定义接口(JDK动态代理必须要有接口) public interface UserService { void addUser(String username); } // ② 目标类(实现接口) public class UserServiceImpl implements UserService { public void addUser(String username) { System.out.println("添加用户:" + username); } } // ③ 动态代理处理器(核心) public class LogHandler implements InvocationHandler { private Object target; // 被代理的目标对象 public LogHandler(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("后置日志:方法执行完毕"); return result; } } // ④ 客户端使用 public class Client { public static void main(String[] args) { UserService target = new UserServiceImpl(); LogHandler handler = new LogHandler(target); // 动态生成代理对象 UserService proxy = (UserService) Proxy.newProxyInstance( target.getClass().getClassLoader(), // 类加载器 target.getClass().getInterfaces(), // 接口数组 handler // 调用处理器 ); proxy.addUser("张三"); } }
执行流程解析
创建代理对象:
Proxy.newProxyInstance()在内存中动态生成代理类的字节码加载代理类:通过类加载器将代理类加载到JVM
调用代理方法:当调用
proxy.addUser()时,实际触发handler.invoke()方法执行增强逻辑:在
invoke()中执行前置/后置增强,然后通过反射调用目标对象的原始方法-1
七、CGLIB动态代理(进阶)
当目标类没有实现任何接口时,JDK动态代理无法使用。CGLIB(Code Generation Library)通过生成目标类的子类来实现代理,弥补了这一短板。-
CGLIB代理原理
CGLIB通过ASM字节码处理框架在运行时动态生成目标类的子类,子类重写所有非final方法,并在方法中插入拦截逻辑。-20
代码示例
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 ProductService { public void create() { System.out.println("创建产品"); } } // ② 方法拦截器 public class LogInterceptor 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; } } // ③ 创建CGLIB代理 public class Client { public static void main(String[] args) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(ProductService.class); // 设置父类为目标类 enhancer.setCallback(new LogInterceptor()); // 设置回调拦截器 ProductService proxy = (ProductService) enhancer.create(); // 生成代理对象 proxy.create(); } }
JDK动态代理 vs CGLIB:核心差异
| 对比维度 | JDK动态代理 | CGLIB动态代理 |
|---|---|---|
| 实现原理 | 基于接口,生成实现了接口的代理类 | 基于继承,生成目标类的子类 |
| 目标类要求 | 必须实现至少一个接口 | 无需接口,但不能是final类 |
| 底层技术 | 反射 + 字节码生成 | ASM字节码框架 |
| 性能特点 | 反射调用有一定开销 | 运行时调用更快,但生成代理类耗时 |
| 方法调用 | 通过反射转发到目标对象 | 通过继承调用父类方法 |
八、底层原理/技术支撑
JDK动态代理的底层秘密
JDK动态代理的核心在于 ProxyGenerator。当调用 Proxy.newProxyInstance() 时:
JDK通过
ProxyGenerator在内存中动态生成一个.java源文件该源文件中的代理类继承
Proxy类,并实现目标对象的所有接口代理类的每个方法内部都会调用
InvocationHandler.invoke()通过
JavaCompiler编译成.class,再用defineClass0加载到JVM-13
CGLIB的底层秘密
CGLIB底层依赖 ASM(字节码操作框架) :
ASM直接读取目标类的二进制字节码
动态生成一个子类,重写所有非final方法
在重写的方法中,调用
MethodInterceptor.intercept()CGLIB还生成 FastClass 机制,通过方法索引直接调用目标方法,避免反射的性能损耗-23
框架应用全景
| 框架/技术 | 使用的代理机制 |
|---|---|
| Spring AOP | 智能选择:有接口用JDK,无接口用CGLIB |
| MyBatis Mapper | JDK动态代理 |
| Feign(RPC) | JDK动态代理 |
| Hibernate懒加载 | CGLIB动态代理 |
九、高频面试题与参考答案
面试题1:什么是代理模式?有哪些分类?
参考答案:代理模式是一种结构型设计模式,通过引入代理对象来控制对目标对象的访问,并在不修改目标对象代码的前提下实现功能增强。按代理类生成时机分为静态代理(编译期手动编写)和动态代理(运行期自动生成),动态代理又分为JDK动态代理(基于接口)和CGLIB动态代理(基于继承)。-71
面试题2:JDK动态代理和CGLIB有什么区别?Spring AOP如何选择?
参考答案:JDK动态代理要求目标类必须实现接口,基于反射机制生成代理类;CGLIB通过继承生成子类,无需接口但无法代理final类。Spring AOP通过DefaultAopProxyFactory自动判断:目标类有接口时优先使用JDK动态代理,无接口或配置proxyTargetClass=true时使用CGLIB。-31
面试题3:为什么JDK动态代理只能代理接口?
参考答案:因为JDK动态代理生成的代理类已经继承了java.lang.reflect.Proxy类。Java是单继承语言,代理类无法再继承其他类,所以只能通过实现接口的方式来代理目标对象。-64
面试题4:CGLIB能代理final类或final方法吗?
参考答案:不能。CGLIB通过生成子类来实现代理,而final类不能被继承,final方法不能被重写,因此CGLIB无法代理final类或final方法。这也是CGLIB的主要限制。-20
面试题5:静态代理和动态代理各自的优缺点是什么?
参考答案:静态代理优点是实现简单、直接调用性能好;缺点是每个被代理类都需要手动编写代理类,代码冗余,扩展性差。动态代理优点是灵活性强,一个代理类可服务多个目标对象,减少代码冗余;缺点是实现复杂,依赖反射或字节码技术,有一定性能开销。-
十、结尾总结
本文围绕代理模式这一Java核心技术,由浅入深梳理了完整知识链路:
核心概念:代理模式通过代理对象实现对目标对象的访问控制和功能增强
静态代理 vs 动态代理:前者编译期手动编写,后者运行期自动生成
JDK动态代理 vs CGLIB:前者基于接口+反射,后者基于继承+ASM字节码
框架应用:Spring AOP根据目标类特性智能选择代理机制
底层原理:JDK依赖ProxyGenerator动态生成字节码,CGLIB依赖ASM
记忆口诀:代理模式做中介,控制增强都不变;静态编译手动写,动态运行自动建;JDK必须接接口,CGLIB子类来实现;Spring智能帮你选,面试牢记这些点。
下期预告:深入Spring AOP源码,揭秘@Transactional注解如何通过动态代理实现声明式事务管理,敬请期待。
参考文献:
「全网最细 + 实战源码案例」设计模式——代理模式,阿里云开发者社区,2025-01-28
Java Proxy代理有哪些常见类型,亿速云,2025-05-26
深入理解 JDK 动态代理 (JDK Dynamic Proxy),CSDN,2025-06-17
深度解析 Spring 源码:探秘 CGLIB 代理的奥秘,华为云社区,2025-02-09
Spring AOP 核心概念全解析:从动态代理到切面编程实战,2025-07-29
代理模式面试题,动力节点
扫一扫微信交流