Spring实现AOP功能的原理:代理模式(JDK动态代理与CGLIB字节码生成技术代理)的理解

devtools/2025/2/13 4:11:19/

JDK 动态代理和 CGLIB 代理是 Java 中两种常见的动态代理技术。它们的核心作用是 在运行时生成代理对象,用于增强原始对象的功能(如 AOP 切面编程、拦截方法调用等)。


JDK 动态代理

JDK 动态代理基于 java.lang.reflect.ProxyInvocationHandler 接口。它 仅能代理实现了接口的类,通过 Proxy.newProxyInstance 方法在运行时生成代理对象。

工作原理

  1. 代理类实现目标接口,并在 invoke 方法中拦截所有方法调用。
  2. 代理对象是 Proxy 类的子类,JVM 在运行时动态生成该类。
  3. 调用方法时,代理对象会调用 InvocationHandlerinvoke 方法。

示例

java">import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;// 1. 定义接口
interface UserService {void save(String name);
}// 2. 目标对象
class UserServiceImpl implements UserService {@Overridepublic void save(String name) {System.out.println("保存用户: " + name);}
}// 3. 创建 InvocationHandler 处理器
class MyInvocationHandler implements InvocationHandler {private final Object target;public MyInvocationHandler(Object target) {this.target = target;}@Overridepublic 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;}
}// 4. 使用动态代理创建代理对象
public class JDKProxyDemo {public static void main(String[] args) {UserService target = new UserServiceImpl();UserService proxy = (UserService) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),new MyInvocationHandler(target));proxy.save("张三");}
}

输出

日志: 方法 save 被调用,参数:张三
保存用户: 张三
日志: 方法 save 调用完成

优缺点

优点

  • 依赖 JDK,直接使用,无需额外库。
  • 线程安全,稳定性较高。

缺点

  • 只能代理实现了接口的类,不能代理普通类。
  • 由于反射调用 invoke性能稍差(比 CGLIB 慢)。

CGLIB 动态代理

CGLIB(Code Generation Library)是一个强大的 字节码生成技术,可以代理 没有实现接口的普通类,其底层依赖 ASM(Java 字节码操作库)。

工作原理

  1. CGLIB 生成 目标类的子类,重写其方法,并在方法调用时增强功能。
  2. 由于 CGLIB 直接操作字节码,比 JDK 代理更快(因为少了反射调用)。
  3. 但由于是继承方式,不能代理 final 方法

示例

需要 引入 CGLIB 依赖

<dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.3.0</version>
</dependency>
java">import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;// 1. 目标类(没有实现接口)
class UserService {public void save(String name) {System.out.println("保存用户: " + name);}
}// 2. CGLIB 代理类
class CglibProxy implements MethodInterceptor {private final Object target;public CglibProxy(Object target) {this.target = target;}@Overridepublic Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {System.out.println("日志: 方法 " + method.getName() + " 被调用,参数:" + args[0]);Object result = proxy.invoke(target, args);System.out.println("日志: 方法 " + method.getName() + " 调用完成");return result;}// 生成代理对象public Object createProxy() {Enhancer enhancer = new Enhancer();enhancer.setSuperclass(target.getClass());enhancer.setCallback(this);return enhancer.create();}
}// 3. 使用 CGLIB 创建代理对象
public class CGLIBProxyDemo {public static void main(String[] args) {UserService target = new UserService();UserService proxy = (UserService) new CglibProxy(target).createProxy();proxy.save("李四");}
}

输出

日志: 方法 save 被调用,参数:李四
保存用户: 李四
日志: 方法 save 调用完成

优缺点

优点

  • 可以代理普通类(不要求接口)。
  • 性能比 JDK 代理更好(不使用反射,而是直接生成字节码)。

缺点

  • 不能代理 final 类或 final 方法(因为 CGLIB 通过继承来生成代理)。
  • 额外依赖 CGLIB,需要额外的 Jar 包(不过 Spring 已内置 CGLIB)。
  • 比 JDK 代理稍微复杂,但更灵活。

JDK 代理 vs CGLIB 代理 对比

特性JDK 动态代理CGLIB 代理
代理方式反射 InvocationHandler继承+ASM 字节码生成
是否需要接口需要(只能代理接口)不需要(可代理普通类)
代理对象类型Proxy 生成的匿名类目标类的子类
是否可代理 final 方法可以不可以
性能较慢(基于反射)较快(直接修改字节码)
依赖JDK 自带需额外依赖 CGLIB
典型应用java.lang.reflect.Proxy、Spring AOP(基于接口)Spring AOP(无接口时)

使用场景总结

  • JDK 动态代理 适用于 已有接口 的情况,比如 Service 层的 UserServiceOrderService
  • CGLIB 动态代理 适用于 无接口 的普通类,比如 UserService 直接写成普通类。

Spring AOP 使用情况

  • 如果被代理的类实现了接口,Spring 默认使用 JDK 动态代理
  • 如果没有接口,Spring 使用 CGLIB 代理@EnableAspectJAutoProxy(proxyTargetClass = true) 强制 CGLIB)。

总结

  • JDK 动态代理 是基于接口的反射机制,适用于 接口代理
  • CGLIB 代理 通过 字节码增强 来生成子类,适用于 普通类代理
  • Spring AOP 结合两者,默认用 JDK,必要时才用 CGLIB。

对于日常开发,如果类有接口,优先使用 JDK 动态代理;否则,使用 CGLIB


http://www.ppmy.cn/devtools/158373.html

相关文章

SQL最佳实践(笔记)

写在前面&#xff1a; 之前baeldung的Java Weekly &#xfeff;Reviews里面推荐了一篇关于SQL优化的文章&#xff0c;正好最近在学习数据库相关知识&#xff0c;记一些学习笔记 原文地址&#xff1a;SQL Best Practices Every Java Engineer Must Know 1. 使用索引 使用索引…

java-LinkedList源码详解

前言&#xff1a; LinkedList 是 Java 中另一个常用的集合类&#xff0c;它基于双向链表实现&#xff0c;支持高效的插入和删除操作&#xff0c;但随机访问性能较差 类定义和成员变量&#xff1a; public class LinkedList<E>extends AbstractSequentialList<E>…

zsh: command not found: conda

场景描述 在 Linux 服务器上使用 zsh 时&#xff0c;如果出现 zsh: command not found: conda 错误&#xff0c;说明你的系统未正确配置 conda 命令&#xff0c;或者你尚未安装 Anaconda/Miniconda。 解决方案 确保已安装 Anaconda 或 Miniconda conda 是 Anaconda 或 Minico…

java后端开发day14--之前练习的总结和思考

1.感受 这两天学点儿新的就直接上手打代码&#xff0c;真的是累死个人。我唯一的感受就是&#xff0c;课听完了&#xff0c;代码也跟着打完了&#xff08;是的&#xff0c;跟着打的&#xff0c;没自己打&#xff09;&#xff0c;感觉自己脑袋里乱乱的&#xff0c;对代码的分区…

【05】RUST常用的集合函数宏类型

文章目录 常用集合VecStringHashMap 宏打印 类型Option<T> 常用集合 Vec 堆上连续内存vector可能出现扩容&#xff0c;把老元素copy到内存新位置 因此不允许let first &v[0];作用域内调用v.push(4); // 定义 let v: Vec<i32> Vec::new(); let v vec![1,…

2025 年 2 月 TIOBE 指数

2025 年 2 月 TIOBE 指数 二月头条:快,更快,最快! 现在,世界需要每秒处理越来越多的数字,而硬件的发展速度却不够快,程序的速度变得越来越重要。话虽如此,快速编程语言在 TIOBE 指数中取得进展也就不足为奇了。编程语言 C++ 最近攀升至第 2 位,Go 已稳居前 10 名,Ru…

HALCON 数据结构

目录 1. HALCON基本数据分类 1.1 图像相关数据 1.1.1 Image(图片) 1.1.2 Region(区域) 1.1.3 XLD(轮廓) 1.2 控制类数据 1.2.1 基本控制数据类型 1.2.2 handle(句柄) 2. 数组与字典 2.1 数组类型及特点 2.1.1 Iconic数组(Objects) 2.1.2 Control数组(Tu…

智能同义词处理与命中优化:提升知识库查询精度

效果展示(环境依赖请参看上一篇文章): qa.json(示例知识): 测试结果: 引言 在构建智能问答系统时,常常遇到用户提问方式多样化的问题。即使问题本质相同,表达方式可能千差万别。为了提高搜索和匹配的准确性,我们需要对原始问题进行扩展,即生成多个同义表达。这…