一、基础信息配置
目标读者:技术入门/进阶学习者、在校学生、面试备考者、Java开发工程师

文章定位:技术科普 + 原理讲解 + 代码示例 + 面试要点,兼顾易懂性与实用性
写作风格:条理清晰、由浅入深、语言通俗、重点突出

核心目标:让读者理解概念、理清逻辑、看懂示例、记住考点
在Java开发中,我们经常遇到这样的场景:需要在核心业务方法前后添加日志打印、事务管理或性能监控,却不想在每个方法里重复写log.info(...),更不希望因为添加这些“旁路功能”而污染原有的业务代码。代理模式(Proxy Pattern) 正是解决这一问题的经典方案,也是Java框架(如Spring AOP、MyBatis等)底层应用最广泛的设计模式之一-1。做题助手ai将从痛点切入,逐步拆解静态代理与动态代理的核心概念、底层原理及高频面试考点,帮助读者建立完整的知识链路。
二、整体结构
h2 痛点切入:为什么需要代理模式?
假设你开发了一个用户服务类UserService,核心业务是新增和删除用户:
// 目标类:真正实现业务逻辑 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); } }
现在需求来了:需要在每次执行addUser和deleteUser前后,打印日志记录操作。
最直观的“笨办法”——直接修改源码:
@Override public void addUser(String username) { System.out.println("〖日志〗开始执行addUser,参数:" + username); // 侵入式添加 System.out.println("数据库新增用户:" + username); System.out.println("〖日志〗addUser执行完毕"); // 侵入式添加 }
这种方式的问题显而易见:
耦合度高:业务逻辑与日志逻辑纠缠在一起,违背“单一职责原则”
代码冗余:每个方法都要重复编写日志代码
扩展性差:如果想换一种日志方式(如写入文件),需要逐个修改
维护困难:系统中有几十上百个Service时,维护成本将急剧上升
这正是代理模式要解决的痛点——在不修改原有代码的前提下,给业务逻辑附加额外功能。代理模式就像代码世界里的“中间人”,它不直接干活,但能帮目标对象“跑腿”,在跑腿的过程中悄悄完成日志、权限、监控这些附加工作-61。
h2 概念A:静态代理(Static Proxy)
标准定义
静态代理(Static Proxy) :指代理类的代码在编译期就已确定,由程序员手动编写或工具生成代理类的源码,再编译成字节码文件。代理类和委托类的关系在运行前就已确定-1-7。
实现方式
静态代理需要满足三个核心要素:
抽象主题(Subject) :通过接口或抽象类声明真实主题和代理对象共同实现的业务方法
真实主题(Real Subject) :实现抽象主题中的具体业务,即目标对象
代理类(Proxy) :提供与真实主题相同的接口,内部持有对真实主题的引用,在调用目标方法前后进行功能增强-1
代码示例:为UserService添加日志的静态代理
// 步骤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执行完毕"); } } // 客户端调用 public class Client { public static void main(String[] args) { UserService target = new UserServiceImpl(); UserService proxy = new UserServiceProxy(target); proxy.addUser("张三"); // 实际调用的是代理类 } }
静态代理的优缺点
| 优点 | 缺点 |
|---|---|
| 实现简单,易于理解 | 每个被代理类都需要一个专属代理类,代码冗余-2 |
| 编译期优化,性能略优-50 | 接口变更时需同步修改代理类,维护成本高 |
| 可灵活控制被代理类的方法 | 目标对象较多时会导致代理类爆炸式增长-6 |
一句话总结:静态代理是“一对一”的专属服务,简单直接但不够灵活。
h2 概念B:动态代理(Dynamic Proxy)
标准定义
动态代理(Dynamic Proxy) :在程序运行时动态生成代理类,代理类的字节码在运行期间生成并加载到JVM中,无需手动编写代理类代码-49。
Java中动态代理有两种主流实现方式:
JDK动态代理(JDK Dynamic Proxy) :基于接口的代理,利用Java原生反射机制,要求目标类必须实现接口
CGLIB动态代理(CGLIB Dynamic Proxy) :基于继承的代理,通过字节码技术生成目标类的子类,不要求目标类实现接口
1. JDK动态代理
JDK动态代理是Java标准库的一部分(java.lang.reflect包),随JDK 1.3引入-31。
核心组件:
Proxy类:提供静态方法newProxyInstance()动态生成代理对象InvocationHandler接口:开发者实现此接口,在invoke()方法中编写增强逻辑
工作原理:
运行时通过
Proxy.newProxyInstance()为接口动态生成代理类的字节码通过类加载器将字节码加载到JVM中
创建代理类实例,并与
InvocationHandler绑定客户端调用代理对象方法时,代理类将调用转发给
InvocationHandler.invoke()在
invoke()中通过反射调用目标方法,并执行前后增强逻辑-12-41
代码示例:
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; // 定义接口(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,封装增强逻辑 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() + ",参数:" + args[0]); // 通过反射调用目标方法 Object result = method.invoke(target, args); // 后置增强:日志 System.out.println("〖日志〗" + method.getName() + "执行完毕"); return result; } } // 使用动态代理 public class Client { 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("张三"); // 调用代理对象方法 } }
2. CGLIB动态代理
CGLIB(Code Generation Library)是一个第三方开源库,通过ASM字节码框架在运行时生成目标类的子类来实现代理,弥补了JDK动态代理的局限性-31。
核心组件:
Enhancer类:核心类,用于创建代理对象,设置父类(目标类)和回调MethodInterceptor接口:开发者实现此接口,在intercept()方法中编写增强逻辑
工作原理:
运行时通过ASM字节码技术生成目标类的子类
代理子类重写父类的所有非
final方法重写的方法内部调用
MethodInterceptor.intercept()执行增强逻辑通过
MethodProxy.invokeSuper()调用父类(目标类)的原始方法-26
代码示例:
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 UserService { public void addUser(String username) { System.out.println("数据库新增用户:" + username); } public void deleteUser(String username) { System.out.println("数据库删除用户:" + username); } } // 自定义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() + ",参数:" + args[0]); // 调用父类(目标类)的原始方法 Object result = proxy.invokeSuper(obj, args); // 后置增强:日志 System.out.println("〖日志〗" + method.getName() + "执行完毕"); return result; } } // 使用CGLIB动态代理 public class Client { 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("张三"); } }
h2 概念关系与区别总结
一句话概括
静态代理是“编译期手写、一对一绑定”的专属代理;动态代理是“运行期生成、一对多复用”的通用代理,其中JDK代理基于接口+反射,CGLIB代理基于继承+字节码。
详细对比表
| 对比维度 | 静态代理 | JDK动态代理 | CGLIB动态代理 |
|---|---|---|---|
| 代理类生成时机 | 编译期 | 运行期 | 运行期 |
| 是否需手动编写代理类 | 是 | 否 | 否 |
| 对目标类的要求 | 需实现接口 | 必须实现接口 | 无需接口(但不能是final类) |
| 底层技术 | 无特殊技术 | 反射 + Proxy | ASM字节码增强 |
| 性能特点 | 编译期优化,性能略优 | JDK 1.8+反射优化后性能较好 | 生成类速度慢,调用速度快 |
| 能否代理final方法 | 可以 | 不能(接口方法无final概念) | 不能(无法继承重写) |
| 依赖 | 无 | Java标准库 | 需引入CGLIB库 |
| 代码复用性 | 差,每类需单独编写 | 好,一个Handler可代理多个目标类 | 好,一个Interceptor可代理多个目标类 |
核心区别记忆口诀
静态提前写,动态运行时;
JDK要接口,CGLIB靠继承;
反射跑得慢,字节码更快;
Spring里混用,框架自动选。
h2 代码示例:静态代理 vs 动态代理直观对比
场景:为订单服务添加日志和权限校验
静态代理方式(需为每个服务手写代理类):
// 订单服务接口 public interface OrderService { void createOrder(String orderId); } // 订单服务实现 public class OrderServiceImpl implements OrderService { @Override public void createOrder(String orderId) { System.out.println("创建订单:" + orderId); } } // 静态代理类——需要为OrderService单独编写 public class OrderServiceProxy implements OrderService { private final OrderService target; public OrderServiceProxy(OrderService target) { this.target = target; } @Override public void createOrder(String orderId) { System.out.println("〖日志〗开始创建订单"); System.out.println("〖权限〗校验用户权限"); target.createOrder(orderId); System.out.println("〖日志〗订单创建完成"); } }
如果系统中有UserService、PaymentService、ProductService等几十个服务,就需要编写几十个类似的代理类——代码量随服务数量线性增长。
JDK动态代理方式(一个Handler通吃所有服务):
public class CommonInvocationHandler implements InvocationHandler { private final Object target; public CommonInvocationHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("〖日志〗开始执行" + method.getName()); System.out.println("〖权限〗校验用户权限"); Object result = method.invoke(target, args); System.out.println("〖日志〗" + method.getName() + "执行完毕"); return result; } } // 一个Handler可代理任意实现了接口的服务 OrderService orderProxy = (OrderService) Proxy.newProxyInstance( orderService.getClass().getClassLoader(), orderService.getClass().getInterfaces(), new CommonInvocationHandler(orderService) ); UserService userProxy = (UserService) Proxy.newProxyInstance( userService.getClass().getClassLoader(), userService.getClass().getInterfaces(), new CommonInvocationHandler(userService) );
动态代理的核心优势在于:一套增强逻辑可以服务于任意多个目标对象,避免了静态代理中代理类数量随服务数量线性增长的问题--49。
h2 底层原理与技术支撑
JDK动态代理的底层机制
JDK动态代理的核心流程可概括为三步走:生成字节码 → 加载字节码 → 创建实例-41。
运行时生成字节码:
Proxy.newProxyInstance()内部通过ProxyGenerator.generateProxyClass()动态生成代理类的字节码(类名通常为$Proxy0、$Proxy1等)类加载器加载:通过指定的
ClassLoader将生成的字节码加载到JVM内存中反射实例化:通过反射创建代理类实例,并将
InvocationHandler注入其中
JDK使用了WeakCache缓存机制管理代理类,对相同(ClassLoader,interfaces)组合只生成一次,避免重复生成-。
关键技术依赖:
反射机制(Reflection) :
Method.invoke()在运行时动态调用目标方法动态类加载:运行时生成类字节码并加载到JVM
CGLIB动态代理的底层机制
CGLIB的核心是基于继承的字节码增强-26:
生成子类:通过ASM字节码框架在内存中生成目标类的子类(类名如
UserService$$EnhancerByCGLIB$$xxx)重写方法:代理子类重写父类的所有非
final方法方法拦截:重写的方法内部调用
MethodInterceptor.intercept(),开发者在此插入增强逻辑FastClass优化:CGLIB生成
FastClass类,通过方法索引直接调用目标方法,避免了JDK动态代理中反射调用的性能损耗-26
关键技术依赖:
ASM字节码框架:用于在内存中动态生成和修改Java字节码
继承机制:通过创建目标类的子类来实现代理
技术支撑总结
JDK动态代理 = 反射 + 动态类加载 + 接口;CGLIB动态代理 = 字节码生成 + 继承 + 方法拦截。二者都依赖于Java运行时的动态特性,是实现Spring AOP等技术的基础设施。
h2 高频面试题与参考答案
面试题1:静态代理和动态代理有什么区别?
参考答案(踩分点) :
| 区别维度 | 静态代理 | 动态代理 |
|---|---|---|
| 代理类创建时机 | 编译期确定,代理类源码存在 | 运行期动态生成,无物理.class文件-50 |
| 代码量 | 每个目标类需一个代理类,代码冗余 | 一个Handler可代理多个目标类,代码复用性高-49 |
| 灵活性 | 接口变更需同步修改代理类 | 灵活,无需手动维护代理类 |
| 性能 | 编译期优化,性能略优 | 运行期有反射/字节码开销(JDK 1.8+已优化,差距极小)-50 |
面试题2:JDK动态代理和CGLIB动态代理有什么区别?(高频星标题)
参考答案(踩分点) :
核心前提:JDK动态代理要求目标类必须实现接口;CGLIB无需接口,但无法代理
final类和final方法-30实现原理:JDK基于反射 +
Proxy+InvocationHandler;CGLIB基于ASM字节码增强 + 继承 +MethodInterceptor性能表现:JDK 1.8+对反射进行优化后,JDK动态代理性能优于CGLIB;低版本JDK中CGLIB性能略优-50
代理方式:JDK是接口代理;CGLIB是子类代理
依赖:JDK是Java标准库,无需额外依赖;CGLIB需引入第三方库
记忆口诀:“JDK要接口、CGLIB靠继承;1.8以上JDK快,以下CGLIB更行。”
面试题3:为什么Spring AOP默认使用JDK动态代理?
参考答案(踩分点) :
Spring AOP的底层实现核心就是动态代理,其默认策略是:目标类有接口时使用JDK动态代理,无接口时使用CGLIB动态代理-50。原因包括:
JDK动态代理是Java标准库的一部分,无需引入额外依赖
符合“面向接口编程”的设计原则
JDK 1.8+版本反射性能已得到显著优化
可通过
<aop:aspectj-autoproxy proxy-target-class="mip907e2b3cdb8eac05 true"/>或@EnableAspectJAutoProxy(proxyTargetClass = true)强制使用CGLIB
面试题4:动态代理在框架中有哪些实际应用场景?
参考答案(踩分点) :
声明式事务管理:Spring通过动态代理在业务方法执行前开启事务,执行后提交事务,异常时回滚
统一日志记录:在方法执行前记录请求入参、方法名称,执行后记录返回结果和耗时
权限校验拦截:在核心方法执行前校验用户权限,无权限则拒绝执行
RPC远程调用:如Feign、Dubbo等框架,通过动态代理实现接口到远程调用的透明转换
懒加载:Hibernate等ORM框架使用CGLIB代理实现关联对象的懒加载-50
面试题5:CGLIB为什么不能代理final类?
参考答案(踩分点) :
CGLIB动态代理的底层原理是通过继承目标类生成子类来实现代理。如果一个类被声明为final,则无法被继承,CGLIB无法创建该类的子类,因此无法代理。同理,final方法也无法被子类重写,CGLIB也无法对这些方法进行代理增强-30。
h2 结尾总结
核心知识点回顾
代理模式:在客户端和目标对象之间插入代理对象,以控制访问并增强功能,不修改原有代码
静态代理:编译期手动编写代理类,简单直接,但代理类数量随服务数量线性增长,维护成本高
JDK动态代理:基于接口+反射,运行期动态生成代理类,要求目标类实现接口
CGLIB动态代理:基于继承+字节码,运行期生成目标类子类,无需接口但不能代理
final类底层技术:JDK依赖反射和动态类加载;CGLIB依赖ASM字节码框架和继承机制
Spring AOP:默认根据是否有接口自动选择代理方式,也可强制使用CGLIB
重点与易错点提醒
易错1:在JDK动态代理的invoke()方法中调用proxy对象的方法会导致无限递归——应始终使用method.invoke(target, args)调用目标方法,而不是调用proxy-11。
易错2:CGLIB无法代理final类和final方法——这是由继承实现机制决定的天然限制。
易错3:JDK动态代理代理对象不能转换为具体实现类类型,只能转换为接口类型——否则会抛出ClassCastException-11。
进阶预告
下一篇将深入讲解 Java反射机制 的底层原理,探讨反射在动态代理、框架注解解析中的核心作用,以及反射的性能优化策略。关注做题助手ai,获取更多Java核心知识点的系统性学习资料。