【动态代理】CGLIB 动态代理的使用及原理

article/2025/10/31 19:43:02

1. CGLIB 动态代理介绍

什么是 CGLIB?

CGLIB是一个功能强大,高性能的代码生成包。它为没有实现接口的类提供代理,为JDK的动态代理提供了很好的补充。

通常可以使用Java的动态代理创建代理,但当要代理的类没有实现接口或者为了更好的性能,CGLIB 是一个好的选择。

CGLIB 的原理

CGLIB 原理:动态生成一个要代理类的子类,子类重写要代理的类的所有不是 final 的方法。在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。

CGLIB 底层:采用ASM字节码生成框架,使用字节码技术生成代理类,比使用 Java 反射效率要高。

2. CGLIB 动态代理使用

CGLIB 动态代理步骤:

  1. 引入 CGLIB 依赖
  2. 定义一个被代理类
  3. 定义一个拦截器并实现接口 MethodInterceptor
  4. 代理工厂类
  5. 通过代理对象调用方法

引入依赖:cglib-nodep-2.2.jar

Student:被代理类

public class Student {public void handOut() {System.out.println("学生交作业。");}
}

CglibProxy:拦截器

public class CglibProxy implements MethodInterceptor {/*** @param o: 代理对象* @param method: 被代理方法* @param params: 方法入参* @param methodProxy: CGLIB方法**/@Overridepublic Object intercept(Object o, Method method, Object[] params, MethodProxy methodProxy) throws Throwable {System.out.println("【增强方法】代理对象正在执行的方法:" + method.getName());Object result = methodProxy.invokeSuper(o, params);return result;}
}

CglibProxyFactory:代理工厂类

public class CglibProxyFactory {public static Object creatCglibProxyObj(Class<?> clazz) {Enhancer enhancer = new Enhancer();// 为加强器指定要代理的业务类(即为下面生成的代理类指定父类)enhancer.setSuperclass(clazz);// 设置回调:对于代理类上所有方法的调用,都会调用CallBack,而Callback则需要实现intercept()方法enhancer.setCallback(new CglibProxy());return enhancer.create();}}

测试:

public class Test {public static void main(String[] args) {Student studentProxy = (Student)CglibProxyFactory.creatCglibProxyObj(Student.class);studentProxy.handOut();}}

运行后,依旧可以增强原功能。

3. CGLIB 动态代理原理

上文中的是通过 enhancer.create 方法调用获取的代理对象,以此为入口深入探究一下 CGLIB 动态代理的实现原理。

Enhancer#create()

public Object create() {this.classOnly = false;this.argumentTypes = null;return this.createHelper();
}

Enhancer#createHelper():调用父类的 create() 方法

private Object createHelper() {//...return super.create(KEY_FACTORY.newInstance(this.superclass != null ? this.superclass.getName() : null, ReflectUtils.getNames(this.interfaces), this.filter, this.callbackTypes, this.useFactory, this.interceptDuringConstruction, this.serialVersionUID));
}

AbstractClassGenerator#create()

protected Object create(Object key) {try {//...if (gen == null) {// 1.生成代理类 byte[] b = this.strategy.generate(this);// 2.获取代理类名称String className = ClassNameReader.getClassName(new ClassReader(b));this.getClassNameCache(loader).add(className);gen = ReflectUtils.defineClass(className, b, loader);}if (this.useCache) {((Map)cache2).put(key, new WeakReference(gen));}var24 = this.firstInstance(gen);} finally {CURRENT.set(save);}return var24;}//...
}

DefaultGeneratorStrategy#generate():生成代理类

public byte[] generate(ClassGenerator cg) throws Exception {ClassWriter cw = this.getClassWriter();this.transform(cg).generateClass(cw);return this.transform(cw.toByteArray());
}

DebuggingClassWriter#toByteArray()

public byte[] toByteArray() {return (byte[])((byte[])AccessController.doPrivileged(new PrivilegedAction() {public Object run() {byte[] b = DebuggingClassWriter.super.toByteArray();if (DebuggingClassWriter.debugLocation != null) {String dirs = DebuggingClassWriter.this.className.replace('.', File.separatorChar);try {// 如果 DebuggingClassWriter.DEBUG_LOCATION_PROPERTY 系统属性被设置,则输出代理类到指定目录(new File(DebuggingClassWriter.debugLocation + File.separatorChar + dirs)).getParentFile().mkdirs();File file = new File(new File(DebuggingClassWriter.debugLocation), dirs + ".class");BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(file));try {out.write(b);} finally {out.close();}if (DebuggingClassWriter.traceEnabled) {file = new File(new File(DebuggingClassWriter.debugLocation), dirs + ".asm");out = new BufferedOutputStream(new FileOutputStream(file));try {ClassReader cr = new ClassReader(b);PrintWriter pw = new PrintWriter(new OutputStreamWriter(out));TraceClassVisitor tcv = new TraceClassVisitor((ClassVisitor)null, pw);cr.accept(tcv, 0);pw.flush();} finally {out.close();}}} catch (IOException var17) {throw new CodeGenerationException(var17);}}return b;}}));
}

生成 CGLIB 字节码文件

由上文可知,把 DebuggingClassWriter.DEBUG_LOCATION_PROPERTY(也就是 cglib.debugLocation)系统属性设置为当前项目的根目录,即可保存 CGLIB 生成的代理类到当前项目根目录下。

设置系统属性配置:

public static void main(String[] args) {System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, System.getProperty("user.dir"));Student studentProxy = (Student)CglibProxyFactory.creatCglibProxyObj(Student.class);studentProxy.handOut();
}

运行代码:
在这里插入图片描述
生成的动态代理类为:

public class Student$$EnhancerByCGLIB$$723acbd8 extends Student implements Factory {private boolean CGLIB$BOUND;private static final ThreadLocal CGLIB$THREAD_CALLBACKS;private static final Callback[] CGLIB$STATIC_CALLBACKS;private MethodInterceptor CGLIB$CALLBACK_0;private static final Method CGLIB$handOut$0$Method;private static final MethodProxy CGLIB$handOut$0$Proxy;//...static void CGLIB$STATICHOOK1() {CGLIB$THREAD_CALLBACKS = new ThreadLocal();CGLIB$emptyArgs = new Object[0];Class var0 = Class.forName("com.zzc.proxy.cglib.Student$$EnhancerByCGLIB$$723acbd8");Class var1;Method[] var10000 = ReflectUtils.findMethods(new String[]{"finalize", "()V", "equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;"}, (var1 = Class.forName("java.lang.Object")).getDeclaredMethods());CGLIB$handOut$0$Method = ReflectUtils.findMethods(new String[]{"handOut", "()V"}, (var1 = Class.forName("com.zzc.proxy.cglib.Student")).getDeclaredMethods())[0];CGLIB$handOut$0$Proxy = MethodProxy.create(var1, var0, "()V", "handOut", "CGLIB$handOut$0");//...}final void CGLIB$handOut$0() {super.handOut();}public final void handOut() {MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;if (var10000 == null) {CGLIB$BIND_CALLBACKS(this);var10000 = this.CGLIB$CALLBACK_0;}if (var10000 != null) {var10000.intercept(this, CGLIB$handOut$0$Method, CGLIB$emptyArgs, CGLIB$handOut$0$Proxy);} else {super.handOut();}}//...static {CGLIB$STATICHOOK1();}
}

说明:

  1. 生成的动态代理类继承了父类 Student,并且实现了接口 Factory
  2. 动态代理类持有 MethodInterceptor
  3. 动态代理类会重写父类 Student 的非 final、private 方法;也会构建自己的方法(cglib 方法),构建方式:CGLIB”+“$父类方法名$
  4. cglib 方法的方法体:super.方法名,直接调用父类;重写方法:它会调用拦截器中的 intercept() 方法
  5. methodProxy.invokeSuper() 方法会调用动态代理类中的 cglib 方法;methodProxy.invoke() 方法会调用动态代理类中的重写方法

CGLIB 动态代理原理外界调用了方法后(studentProxy.handOut();),由于父类 Student 被子类(动态代理类)给继承了(已经重写了 handOut()),所以,会调用动态代理类中的 handOut() 方法。而在这个重写的方法中,又会去调用 MethodInterceptor#intercept() 方法。在这个方法中,功能增强后,再去调用动态代理中的 cglib 方法,而此方法又会去调用父类中的方法。

4. JDK 动态代理和 CGLIB 动态代理比较

4.1 区别

总结一下两者的区别吧:

  1. JDK 动态代理基于接口,CGLIB 动态代理基于类。因为 JDK 动态代理生成的代理类需要继承 java.lang.reflect.Proxy,所以,只能基于接口;CGLIB 动态代理是根据类创建此类的子类,所以,此类不能被 final 修饰
  2. JDK 和 CGLIB 动态代理都是在运行期生成字节码。而 JDK 是直接写 Class 字节码;而 CGLIB 使用 ASM 框架写 Class 字节码(不鼓励直接使用ASM,因为它要求你必须对 JVM 内部结构包括 class 文件的格式和指令集都很熟悉)
  3. JDK 通过反射调用方法,CGLIB 通过 FastClass 机制(下一篇再将)直接调用方法。所以,CGLIB 执行的效率较高
  4. JDK 动态代理是利用反射机制生成一个实现代理接口的类(这个类看不见摸不着,在 jvm 内存中有这个类),在调用具体方法前调用 InvokeHandler来处理。核心是实现 InvocationHandler接口,使用 invoke()方法进行面向切面的处理,调用相应的通知;CGLIB 动态代理是利用 asm 开源包,对代理对象类的 class 文件加载进来,通过修改其字节码生成子类来处理。核心是实现 MethodInterceptor 接口,使用 intercept() 方法进行面向切面的处理,调用相应的通知。

4.2 优缺点

劣势:

  1. JDK:JDK的动态代理机制只能代理实现了接口的类,而不能实现接口的类就不能实现JDK的动态代理
  2. CGLIB:CGLIB 的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对 final 修饰的类进行代理

优势:

  1. JDK:最小化依赖关系,减少依赖意味着简化开发和维护,JDK本身的支持,可能比 cglib 更加可靠
  2. JDK:平滑进行JDK版本升级,而字节码类库通常需要进行更新以保证在新版Java 上能够使用。代码实现简单
  3. CGLIB:从某种角度看,限定调用者实现接口是有些侵入性的实践,类似cglib动态代理就没有这种限制。只操作我们关心的类,而不必为其他相关类增加工作量。另外高性能。

5. 动态代理在 Spring 中的应用

Spring 应用:

  1. 如果目标对象实现了接口,默认情况下 Spring 会采用 JDK 的动态代理实现 AOP
  2. 如果目标对象实现了接口,Spring 也可以强制使用 CGLIB 实现 AOP
  3. 如果目标对象没有实现接口,必须采用 CGLIB 实现动态代理,当然 Spring 可以在 JDK 动态代理和 CGLIB 动态代理之间转换

http://chatgpt.dhexx.cn/article/Eq04jTf2.shtml

相关文章

CGLIB详解(最详细)

转载地址:https://blog.csdn.net/danchu/article/details/70238002 什么是CGLIB CGLIB是一个强大的、高性能的代码生成库。其被广泛应用于AOP框架&#xff08;Spring、dynaop&#xff09;中&#xff0c;用以提供方法拦截操作。Hibernate作为一个比较受欢迎的ORM框架&#xff0c…

GPG error解决方案

问题: sudo apt-get update时报错GPG error 解决方案: // F42ED6FBAB17C654是根据你报错那一行确定的 sudo gpg --keyserver keyserver.ubuntu.com --recv F42ED6FBAB17C654 sudo gpg --export --armor F42ED6FBAB17C654 | sudo apt-key add -然后: sudo apt-get update

GPG Keys are configured as: file:///etc/pki/rpm-gpg/RPM-GPG-KEY-mysql

阿里云CentOS 7.9 64位 搭建网站踩坑实录 问题1.GPG Keys are configured as: file:///etc/pki/rpm-gpg/RPM-GPG-KEY-mysql Failing package is: mysql-community-libs-compat-5.7.37-1.el7.x86_64 GPG Keys are configured as: file:///etc/pki/rpm-gpg/RPM-GPG-KEY-mysql …

gpg文件加密解密

使用Ubuntu对文件进行gpg格式的加密和解密 安装 下载源码安装  ./configure   make   make install 命令安装 Debian环境 sudo apt-get install gnupg Fedora 环境 yum install gnupg 加密 gpg -c abc.txt 会让输入一个加密的密码&#xff0c;需要自己输入 解密 gpg -o …

GPG(GnuPG)的安装和使用

基于网络的开源项目&#xff0c;能给用户带来在公共标准基础上的自由发挥&#xff0c;并且能很好地给每个自愿人士提供了共享贡献的机会。但是&#xff0c;同时也因为大众化给使用共享的程序员或团队带来了安全性问题。 当程序员从中央仓库下载第三方构件的时候&#xff0c;下载…

gpg使用

https://blog.csdn.net/weixin_42559321/article/details/82147888 https://www.cnblogs.com/wanghongli/archive/2018/01/08/8241809.html rpm2cpio *.rpm | cpio -imd       #解压一个rpm包rpm -ivh *.rpm --force       #强制安装这个rpm包rpm -iv…

如何在Git中使用GPG

开篇之前&#xff0c;先给大伙看点东西 是不是很想要&#xff1f;你找对地方了! 下面是教程&#xff1a; 在 “开始”菜单 打开Git Bash 输入 gpg --gen-key 显示如下 $ gpg --gen-keygpg (GnuPG) 2.2.13-unknown; Copyright (C) 2019 Free Software Foundation, Inc.This …

成功解决gpg: 找不到有效的 OpenPGP 数据

在Ubuntu系统上安装docker时出现gpg: 找不到有效的 OpenPGP 数据的报错 解决方案&#xff1a; wget https://download.docker.com/linux/ubuntu/gpg sudo apt-key add gpg随后再次执行下载指令&#xff0c;解决报错 成功解决gpg: 找不到有效的 OpenPGP 数据的报错 欢迎小伙…

GPG Overview

Overview PGP目前支持的算法 非对称算法: RSA, ELG, DSA, ECDH, ECDSA, EDDSA对称算法: IDEA, 3DES, CAST5, BLOWFISH, AES, AES192, AES256, TWOFISH, CAMELLIA128, CAMELLIA192, CAMELLIA256哈希算法: SHA1, RIPEMD160, SHA256, SHA384, SHA512, SHA224压缩算法: Uncompres…

GPG 使用初步

GPG 使用初步 1. PGP 软件的安装 PGP 的版本有很多&#xff0c;但由于其商业软件的特性&#xff0c;不能自由使用&#xff0c;自由软件基金会决定开发一个 PGP 的替代品&#xff0c;取名为 GnuPG &#xff0c;这就是 PGP 的由来   GPG 是基于命令行的程序&#xff0c;主要面…

gpg加解密软件学习

为什么要学习gpg呢&#xff1f;因为要在Linux下把一个邮箱的密码加密&#xff0c;不让其他人看到该邮箱真正的密码。 为了不让其他人看到真正的邮箱密码&#xff0c;我们需要对其进行加密。 加密的方式是先把密码先写到一个文件A中&#xff0c;然后使用相关的加密软件对该文件…

java动态代理

java动态代理实现与原理详细分析 原文地址 关于Java中的动态代理&#xff0c;我们首先需要了解的是一种常用的设计模式--代理模式&#xff0c;而对于代理&#xff0c;根据创建代理类的时间点&#xff0c;又可以分为静态代理和动态代理。 一、代理模式 代理模式是常用的java…

动态规划 --- 算法思想介绍

一.动态规划的基本概念 动态规划在五种算法设计方法中难度最大&#xff0c;它建立在最优原则的基础上.采用动态规划方法&#xff0c;可以高效地解决许多用贪婪算法或分治法无法解决的问题.动态规划(dynamic programming)属运筹学中的规划论分支&#xff0c;是求解决策过程最优…

动态规划算法详解

动态规划算法通常用于求解具有最优性质的问题 基本概念 动态规划过程是&#xff1a;每次决策依赖于当前状态&#xff0c;又随即引起状态的转移。一个决策序列就是在变化的状态中产生出来的&#xff0c;所以&#xff0c;这种多阶段最优化决策解决问题的过程就称为动态规划(DP)。…

动态规划原理

1. 基本概念 动态规划通过拆分问题&#xff0c;将问题拆分成许多的子问题&#xff0c;定义问题状态和状态之间的关系&#xff08;即状态转移方程或递推公式&#xff09;&#xff0c;使得问题能够以递推&#xff08;或者说分治&#xff09;的方式去解决。按顺序求解子问题&…

动态代理详解

想要更加透彻的理解动态代理&#xff0c;首先要熟悉下静态代理 一、静态代理 总结来说&#xff1a;目标类和代理类实现了相同的接口&#xff0c;在代理类中依赖了目标类&#xff0c;代理类的方法中调用了目标类的方法&#xff0c;并做了一些增强性的工作。 1、实现静态代理&…

CAD动态块制作

CAD动态块制作 拉伸动态块柜体A拉伸动态块制作第一步&#xff1a;制作柜体A第二步&#xff1a;进入块编辑器编辑第三步&#xff1a;关闭块编辑器 柜体B拉伸动态块制作第一步&#xff1a;制作柜体B第二步&#xff1a;进入块编辑器第三步&#xff1a;关闭块编辑器 可见性动态块第…

数据有效性 动态选择

EXCEL有两列数据栏&#xff0c;A列和B列&#xff0c;都是通过下来框来选择&#xff0c;要求A列选择完成后&#xff0c;B列动态调整可选择的内容。例如&#xff1a;第一例选择“AA”&#xff0c;第二列可供选择的范围是“a-d”&#xff1b;第一例选择“BB”&#xff0c;第二列可…

OTP 动态口令验证

OTP 动态口令验证。 简介 动态口令&#xff08;OTP&#xff0c;One-Time Password&#xff09;又称一次性密码&#xff0c;是使用密码技术实现的在客户端和服务器之间通过共享秘密的一种认证技术&#xff0c;是一种强认证技术&#xff0c;是增强目前静态口令认证的一种非常方…

loj#122.「强制在线」动态图连通性

loj#122. 「强制在线」动态图连通性 UPD:(7个月以后)这代码被叉了,我不想改了( negii真dl 然后发现这格式一更新…啊我的公式和链接怎么假掉了( csdn[] 算了不管了…反正这样子的话之后也不会用了( 题意 N 个点,M 次操作,支持加边/删边/询问两点间连通性。 强制在线。…