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.targettarget;}OverridepublicObjectinvoke(Objectproxy,Methodmethod,Object[]args)throwsThrowable{// 前置增强 System.out.println(【日志】开始执行: method.getName());System.out.println(【日志】参数: Arrays.toString(args));// 调用目标方法 Objectresultmethod.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){UserServiceuserServicenewUserServiceImpl();// 原始对象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不是 invokeObjectresultproxy.invokeSuper(obj,args);// 后置增强 System.out.println(【日志】执行完毕: method.getName());returnresult;}}// 3. 生成代理对象EnhancerenhancernewEnhancer();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.ProxyCGLIB 库基于 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 类不小心标了finalAOP 直接失效而且不会报错方法会静默跳过增强排查很痛苦。七、思维导图速览动态代理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及原因