Spring知识梳理(5)
Spring知识梳理(5)
作者:没有四次元口袋的蓝胖
日期:2026-07-17
标签:Java, Spring, 动态代理, JDK代理, CGLIB
一、为什么需要动态代理?
AOP 的底层就是动态代理。不修改原始类的代码,在运行时生成一个代理对象,在方法调用前后插入增强逻辑。
原始对象:UserService(只有业务逻辑) 代理对象:UserService$Proxy(业务逻辑 + 日志 + 事务 + 权限...) 调用方拿到的永远是代理对象,无感知地享受增强功能。Spring 提供了两种代理方式:JDK 动态代理和CGLIB 代理。
二、JDK 动态代理(必须手写)
2.1 前提条件
目标类必须实现至少一个接口。
2.2 核心类
java.lang.reflect.Proxy—— 生成代理对象java.lang.reflect.InvocationHandler—— 定义增强逻辑
2.3 完整代码
// 1. 定义接口publicinterfaceUserService{voidsave();Stringquery(Stringname);}// 2. 目标类实现接口publicclassUserServiceImplimplementsUserService{@Overridepublicvoidsave(){System.out.println("保存用户");}@OverridepublicStringquery(Stringname){System.out.println("查询用户: "+name);return"用户: "+name;}}// 3. 编写 InvocationHandler(增强逻辑)publicclassLogInvocationHandlerimplementsInvocationHandler{privatefinalObjecttarget;// 持有原始对象的引用publicLogInvocationHandler(Objecttarget){this.target=target;}@OverridepublicObjectinvoke(Objectproxy,Methodmethod,Object[]args)throwsThrowable{// ===== 前置增强 =====System.out.println("【日志】开始执行: "+method.getName());System.out.println("【日志】参数: "+Arrays.toString(args));// ===== 调用目标方法 =====Objectresult=method.invoke(target,args);// ↑ 这里的 method.invoke(target, args) 就是反射调用原始对象的方法// ===== 后置增强 =====System.out.println("【日志】返回值: "+result);System.out.println("【日志】执行完毕: "+method.getName());returnresult;// 返回原始结果(也可以修改后返回)}}// 4. 生成代理对象publicclassProxyFactory{publicstaticObjectcreateProxy(Objecttarget){returnProxy.newProxyInstance(target.getClass().getClassLoader(),// 类加载器target.getClass().getInterfaces(),// 目标类实现的所有接口newLogInvocationHandler(target)// 增强逻辑处理器);}}// 5. 使用publicclassMain{publicstaticvoidmain(String[]args){UserServiceuserService=newUserServiceImpl();// 原始对象UserServiceproxy=(UserService)ProxyFactory.createProxy(userService);// 代理对象proxy.save();// 输出:// 【日志】开始执行: save// 【日志】参数: []// 保存用户// 【日志】返回值: null// 【日志】执行完毕: saveproxy.query("张三");// 输出:// 【日志】开始执行: query// 【日志】参数: [张三]// 查询用户: 张三// 【日志】返回值: 用户: 张三// 【日志】执行完毕: query}}2.4 关键点总结
Proxy.newProxyInstance(classLoader, interfaces, handler) │ │ │ │ │ └── 增强逻辑写在这里 │ └── 代理对象实现这些接口 └── 用哪个类加载器加载- 代理对象和原始对象实现相同的接口,对调用方透明
invoke()中的method.invoke(target, args)是反射调用原始对象- 一个
InvocationHandler可以代理任何实现了接口的对象
三、CGLIB 代理
3.1 前提条件
不需要接口,通过生成目标类的子类来实现代理。
3.2 核心类
org.springframework.cglib.proxy.Enhancer—— 生成代理对象org.springframework.cglib.proxy.MethodInterceptor—— 定义增强逻辑
3.3 完整代码
// 1. 目标类(不需要接口!)publicclassOrderService{publicvoidcreateOrder(){System.out.println("创建订单");}publicStringgetOrder(intid){System.out.println("查询订单: "+id);return"订单#"+id;}}// 2. 编写 MethodInterceptor(增强逻辑)publicclassLogMethodInterceptorimplementsMethodInterceptor{@OverridepublicObjectintercept(Objectobj,Methodmethod,Object[]args,MethodProxyproxy)throwsThrowable{// ===== 前置增强 =====System.out.println("【日志】开始执行: "+method.getName());// ===== 调用目标方法 =====// 注意:这里用 invokeSuper,不是 invoke!Objectresult=proxy.invokeSuper(obj,args);// ===== 后置增强 =====System.out.println("【日志】执行完毕: "+method.getName());returnresult;}}// 3. 生成代理对象Enhancerenhancer=newEnhancer();enhancer.setSuperclass(OrderService.class);// 设置父类enhancer.setCallback(newLogMethodInterceptor());// 设置增强逻辑OrderServiceproxy=(OrderService)enhancer.create();// 4. 使用proxy.createOrder();proxy.getOrder(1);3.4 CGLIB 的本质
OrderService(原始类) ↑ │ 继承 │ OrderService$$EnhancerByCGLIB(代理类) - 重写了所有非 final 方法 - 每个方法内部先执行增强逻辑,再调用 super.method() 执行原始逻辑四、JDK vs CGLIB 全面对比(面试必背)
| 维度 | JDK 动态代理 | CGLIB 代理 |
|---|---|---|
| 前提 | 目标类必须实现接口 | 不需要接口 |
| 底层机制 | java.lang.reflect.Proxy | CGLIB 库,基于 ASM 字节码框架 |
| 代理方式 | 接口代理(实现相同接口) | 子类继承(生成子类重写方法) |
| 核心接口 | InvocationHandler.invoke() | MethodInterceptor.intercept() |
| 调用目标 | method.invoke(target, args) | proxy.invokeSuper(obj, args) |
| 性能 | JDK 8+ 性能已很好 | 生成代理类稍慢,调用性能略快 |
| 限制 | 只能代理接口方法 | 不能代理 final 类 / final 方法 |
| 依赖 | JDK 自带,无需额外依赖 | 需要引入 CGLIB 库 |
| Spring 默认 | Spring Boot 1.x 默认 | Spring Boot 2.x+ 默认 |
代码对比速记
JDK 动态代理 CGLIB 代理 ───────────── ───────────── 需要接口 不需要接口 Proxy.newProxyInstance() Enhancer.create() InvocationHandler.invoke() MethodInterceptor.intercept() 反射调用 method.invoke(target) 继承调用 proxy.invokeSuper(obj) 只能代理接口方法 能代理所有非 final 方法 不能代理 final 类 不能代理 final 类/final 方法五、Spring 如何选择代理方式?
目标类实现了接口? ├── 是 → 默认用 JDK 动态代理 │ (Spring Boot 2.x+ 可通过配置强制用 CGLIB) └── 否 → 必须用 CGLIBSpring Boot 2.x+ 默认使用 CGLIB 的原因:
@Configuration类通常没有接口,需要 CGLIB 代理- 统一使用 CGLIB 可以避免 JDK 代理的一些边界问题
- CGLIB 调用性能略优
配置方式
# application.ymlspring:aop:proxy-target-class:true# 强制使用 CGLIB# proxy-target-class: false # 有接口用 JDK,没接口用 CGLIB六、为什么 CGLIB 不能代理 final?
这是面试高频追问,原理很简单:
CGLIB 的本质:生成目标类的子类,重写方法来插入增强逻辑 class OrderService$$EnhancerByCGLIB extends OrderService { @Override public void createOrder() { // 重写父类方法 // 增强逻辑 super.createOrder(); // 调用父类原始方法 // 增强逻辑 } }final类 → 不能被继承 → 无法生成子类 →不能代理final方法 → 不能被重写 → 无法插入增强逻辑 →不能代理private方法 → 子类不可见 →不能代理
实际踩坑:如果 Service 类不小心标了
final,AOP 直接失效,而且不会报错(方法会静默跳过增强),排查很痛苦。
七、思维导图速览
动态代理(AOP 的底层) ├── JDK 动态代理 │ ├── 前提:目标类实现接口 │ ├── 核心:Proxy + InvocationHandler │ ├── invoke() 中写增强逻辑 │ ├── method.invoke(target, args) 反射调用 │ └── JDK 自带,无需额外依赖 │ ├── CGLIB 代理 │ ├── 前提:不需要接口 │ ├── 核心:Enhancer + MethodInterceptor │ ├── intercept() 中写增强逻辑 │ ├── proxy.invokeSuper(obj, args) 继承调用 │ ├── 本质:生成子类,重写方法 │ ├── 限制:不能代理 final 类/方法 │ └── Spring Boot 2.x+ 默认使用 │ └── Spring 选择策略 ├── 有接口 → 默认 JDK(可配置强制 CGLIB) └── 无接口 → 必须 CGLIB八、写在最后
面试回答模板
“Spring AOP 的底层是动态代理。如果目标类实现了接口,默认用 JDK 动态代理,通过 Proxy + InvocationHandler 实现,在 invoke() 方法中用反射调用原始对象的方法。如果没有接口,用 CGLIB 生成子类来代理,通过 MethodInterceptor 的 intercept() 方法拦截调用,用 invokeSuper 调用父类方法。CGLIB 不能代理 final 类和 final 方法,因为它本质是生成子类重写方法。Spring Boot 2.x 之后默认用 CGLIB。”
学习建议
- JDK 动态代理代码要能手写,面试可能让白板写
- CGLIB理解原理即可,重点记住"不能代理 final"及原因
