【java】CGLIB动态代理原理

news2024/11/19 9:24:27

文章目录

  • 1. 简介
  • 2. 示例
  • 3. 原理
  • 4. JDK动态代理与CGLIB动态代理区别(面试常问)

1. 简介

CGLIB的全称是:Code Generation Library。

CGLIB是一个强大的、高性能、高质量的代码生成类库,它可以在运行期扩展Java类与实现Java接口,
底层使用的是字节码处理框架ASM。

Github地址:github.com/cglib/cglib。

CGLIB的Maven坐标如下所示:

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>

2. 示例

首先,新增一个类:

public class Coder {
    public void work() {
        System.out.println("认真写bug……");
    }
}

然后,自定义一个方法拦截器,实现net.sf.cglib.proxy.MethodInterceptor接口并重写intercept方法:

public class AttendanceMethodInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("上班打卡……");

        Object result = proxy.invokeSuper(obj, args);

        System.out.println("下班打卡……");

        return result;
    }
}

重点看下Object result = proxy.invokeSuper(obj, args);,该行代码最终会执行真正的目标方法,在这前后,我们可以添加一些增强逻辑。

然后,新建个测试类,看下CGLIB动态代理如何使用:

public class CglibProxyTest {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Coder.class);
        enhancer.setCallback(new AttendanceMethodInterceptor());
        // 创建代理对象
        Object object = enhancer.create();

        Coder coder = (Coder) object;
        coder.work();
    }
}

运行以上代码,效果如下图所示:
在这里插入图片描述

从运行结果可以看出,在目标方法的前后,执行了自定义的操作。

3. 原理

看下上面的测试类代码,首先是创建了一个net.sf.cglib.proxy.Enhancer对象,然后调用了setSuperclass()方法将enhancer对象的父类设置为Coder类:
在这里插入图片描述

紧接着调用了setCallback()方法将enhancer对象的方法拦截器设置为自定义的AttendanceMethodInterceptor
在这里插入图片描述

然后是调用enhancer对象的create()方法来生成一个代理对象。

先打印下,简单看下这个代理类的信息:
在这里插入图片描述

图中的com.zwwhnly.mybatisplusdemo.cglibproxy.Coder$$EnhancerByCGLIB$$8e91f654就是CGLIB生成的代理类的名称。

那么这个代理类具体是什么样子呢?

在上面的测试类代码中(Object object = enhancer.create();代码之前)添加以下一行代码:

System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "./cglib");

然后再次运行,会看到项目根目录下生成了一个cglib文件夹,自动生成的代理类就包含在其中:
在这里插入图片描述

可以看到一共生成了5个类,这里重点关注下红色标记的3个类。

先看下Coder$$EnhancerByCGLIB$$8e91f654.class,这个类就是自动生成的代理类:

可以看出Coder$$EnhancerByCGLIB$$8e91f654.class继承了Coder类(也就是说自动生成的代理类其实是被代理类的一个子类),并且重写了Coder类的work()方法,重写后的work()方法会调用自定义的方法拦截器AttendanceMethodInterceptor里的intercept()方法。

然后看下Coder$$EnhancerByCGLIB$$8e91f654$$FastClassByCGLIB$$4e5eb5aa,从名称上可以看出这个类的前半段和上面的类的名称是一样的,后半段拼接上了$$FastClassByCGLIB$$4e5eb5aa,从功能上说,这个类是上面的代理类的索引类,重点关注下里面的getIndex()方法和invoke()方法:
在这里插入图片描述

最后看下Coder$$FastClassByCGLIB$$398819d0,这个类是被代理类Coder的索引类,重点也是关注下里面的getIndex()方法和invoke()方法:
在这里插入图片描述

知道了这3个类的作用后,再一步一步看下示例代码中coder.work();的调用过程,因为coder是生成的代理类的实例,所以coder.work();首先调用的是Coder$$EnhancerByCGLIB$$8e91f654的work()方法:
在这里插入图片描述

这里的var10000是自定义的方法拦截器AttendanceMethodInterceptor,所以执行的是红色截图里的intercept()方法,也就是:
在这里插入图片描述

然后看下invokeSuper()方法:
在这里插入图片描述

首先执行的是init()方法,在该方法内部对fastClassInfo字段进行了赋值:
在这里插入图片描述

从上图可以看出,fci.f1是自动生成的Coder类的索引类Coder$$FastClassByCGLIB$$398819d0,所以fci.i1 = fci.f1.getIndex(sig1);其实执行的是的Coder$$FastClassByCGLIB$$398819d0的getIndex()方法:
在这里插入图片描述

fci.f2是自动生成的代理类的索引Coder$$EnhancerByCGLIB$$8e91f654$$FastClassByCGLIB$$4e5eb5aa,
所以fci.i2 = fci.f2.getIndex(sig2);其实执行的是的Coder$$EnhancerByCGLIB$$8e91f654$$FastClassByCGLIB$$4e5eb5aa的getIndex()方法:
在这里插入图片描述

看完init()方法后再回到invokeSuper()方法:
在这里插入图片描述

上图中的FastClassInfo fci = fastClassInfo;使用到的字段fastClassInfo在init()方法内部已经赋过值,
fci.f2其实是自动生成的代理类的索引类Coder$$EnhancerByCGLIB$$8e91f654$$FastClassByCGLIB$$4e5eb5aa,fci.i2值是1,

所以fci.f2.invoke(fci.i2, obj, args);实际执行的是:
在这里插入图片描述

这里的var10000其实是自动生成的代理类Coder$$EnhancerByCGLIB$$8e91f654的实例,所以接着调用的是
代理类Coder$$EnhancerByCGLIB$$8e91f654的CGLIB$work$0()方法:
在这里插入图片描述

这里的super指的是Coder类,所以super.work();实际执行的是Coder类的work()方法:
在这里插入图片描述

综上所述,coder.work();的调用顺序依次是:

代理类—>自定义方法拦截器—>代理类索引类getIndex()方法–>代理类索引类invoke()方法—>代理类—>被代理类。

4. JDK动态代理与CGLIB动态代理区别(面试常问)

关于JDK动态代理,可以查看上一篇博客:JDK动态代理原理。

了解了JDK动态代理和CGLIB动态代理的原理后,现在来比较下两者的区别,这也是面试时几乎必问的一道面试题。

  • 使用JDK动态代理,被代理类必须要实现接口,使用CGLIB动态代理,被代理类可以不实现接口

    原因分析:
    JDK动态代理生成的代理类继承了java.lang.reflect.Proxy,因为Java是单继承的,如果不通过实现接口的形式,
    无法对类进行扩展。
    CGLIB动态代理生成的代理类实际上是被代理类的子类,所以被代理类可以不实现接口。

  • 自动生成类的数量不同

    JDK动态代理只会生成1个代理类,一般情况下名称为:com.sun.proxy.$Proxy0
    CGLIB动态代理会生成好几个类,核心的3个分别是:

    • 代理类:被代理类的子类,名称格式为Coder$$EnhancerByCGLIB$$8e91f654,包名和被代理类包名一致。
    • 代理类的索引类:名称格式为Coder$$EnhancerByCGLIB$$8e91f654$$FastClassByCGLIB$$4e5eb5aa
      包名和被代理类包名一致。
    • 被代理类的索引类:名称格式为Coder$$FastClassByCGLIB$$398819d0,包名和被代理类包名一致。
  • 生成代理类技术不同

    JDK动态代理使用JDK自带的ProxyGenerator类生成字节码文件。
    CGLIB动态代理使用ASM框架生成字节码文件。

  • 调用方式不同

    JDK动态代理:代理类—>InvocationHandler.invoke()—>被代理类方法(用到了反射)。
    CGLIB动态代理:代理类—>MethodInterceptor.intercept()—>代理类索引类getIndex()—>
    代理类索引类invoke()—>代理类—>被代理类。(直接调用)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/444792.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【SAP Abap】X-DOC:SE43 - SAP 用户菜单定义

X-DOC&#xff1a;SE43 - SAP 用户菜单定义 1、菜单结构2、菜单配置3、菜单自定义4、实践演示 1、菜单结构 SAP的菜单栏&#xff0c;支持按层次结构配置&#xff0c;包括以下两个概念&#xff1a; &#xff08;1&#xff09;菜单项&#xff1a;具体的一个菜单目录或菜单项&…

爬虫日常-12306自动购票程序

文章目录 前言页面分析代码设计 前言 hello兄弟们&#xff0c;最近在钻研新鲜知识&#xff0c;一不留神没想起来更新。今天我们顺着前面几篇文章完整的写出一篇12306的自动购票软件。 首先把我们已经写完的前期步骤代码再顺一遍 from selenium.webdriver import Chrome from…

阿里大佬都在熬夜肝的Java程序优化笔记,程序性能提高了5倍!

前言 此笔记从软件设计、编码和JVM等维度阐述性能优化的方法和技巧&#xff0c;分享资深架构师Java程序性能优化的宝贵经验&#xff0c;专注于Java应用程序的优化方法、技巧和思想&#xff0c;并深度剖析JDK部分的实现。具有较强的层次性和连贯性&#xff0c;深入剖析软件设计…

Word控件Spire.Doc 【脚注】教程(5):单词脚注的位置与数字格式

Spire.Doc for .NET是一款专门对 Word 文档进行操作的 .NET 类库。在于帮助开发人员无需安装 Microsoft Word情况下&#xff0c;轻松快捷高效地创建、编辑、转换和打印 Microsoft Word 文档。拥有近10年专业开发经验Spire系列办公文档开发工具&#xff0c;专注于创建、编辑、转…

1.19 从0开始学习Unity游戏开发--扩展编辑器

在之前的篇章里面&#xff0c;我们一直在编辑器里面干活&#xff0c;然后做好资源的编辑和代码开发后&#xff0c;我们可以直接在编辑器内点击那个播放按钮就能真实的把游戏跑起来&#xff0c;但是有时候&#xff0c;我们可能希望在菜单里面加个按钮&#xff0c;这样我们可以直…

C++ STL学习之【容器适配器】

✨个人主页&#xff1a; 夜 默 &#x1f389;所属专栏&#xff1a; C修行之路 &#x1f38a;每篇一句&#xff1a; 图片来源 A year from now you may wish you had started today. 明年今日&#xff0c;你会希望此时此刻的自己已经开始行动了。 文章目录 &#x1f307;前言&a…

内存泄漏动态检测(valgrind)

初步判断是否有泄漏 Linux 内存性能优化 —— 高内存使用及内存泄漏排查 比如该文的介绍&#xff0c;需要初步了解top free -h等命令&#xff1b; 主要看free 内存泄漏检测方法&#xff1a; 静态我常用的是cppcheck&#xff1b; 动态的 Linux下内存泄漏定位方法 这个文章…

Node.js 一:基础

1.node.js是什么&#xff1a; 2.node.js的作用&#xff1a; 1.服务器应用&#xff1a;b站.. 2.开发工具类应用&#xff1a;webpack&#xff0c;bable等等 3.桌面端应用&#xff1a;vscode&#xff0c;postman等 这些应用都是基于electron框架开发&#xff0c;electron基于node…

【设计模式】23种设计模式之行为型模式

一、模板方法模式 1、豆浆制作问题 编写制作豆浆的程序&#xff0c;说明如下: 1&#xff09;、制作豆浆的流程 选材--->添加配料--->浸泡--->放到豆浆机打碎 2&#xff09;、通过添加不同的配料&#xff0c;可以制作出不同口味的豆浆 3&#xff09;、选材、浸泡和放…

拥抱还是革命,ChatGPT时代 AI专家给出15条科研生存之道

来源&#xff1a;专知 微信号&#xff1a;Quan_Zhuanzhi 你是学术机构的人工智能研究员吗?你是否担心自己无法应对当前人工智能的发展步伐?您是否觉得您没有(或非常有限)访问人工智能研究突破所需的计算和人力资源?你并不孤单; 我们有同样的感觉。越来越多的人工智能学者不…

vuepress-yarn-nodes-静态网页_个人博客搭建

nodes官网&#xff1a;https://nodejs.org/en 先下载nodes进行安装&#xff0c;一般nodes会自带包管理器npm&#xff0c;注意npm与nodes的对应关系&#xff0c;除了npm之外还有yarn包管理器&#xff0c;一般会用npm安装这个包 npm install --global yarnnpm降低版本的方法 np…

LeetCode 1026. 节点与其祖先之间的最大差值

【LetMeFly】1026.节点与其祖先之间的最大差值 力扣题目链接&#xff1a;https://leetcode.cn/problems/maximum-difference-between-node-and-ancestor/ 给定二叉树的根节点 root&#xff0c;找出存在于 不同 节点 A 和 B 之间的最大值 V&#xff0c;其中 V |A.val - B.val…

《花雕学AI》24:如何用万能Prompt公式与ChatGPT进行高效的对话测试

引言 你是否想要与人工智能进行有趣、有价值、有说服力的对话&#xff1f;你是否想要使用ChatGPT这个强大而灵活的对话生成器来创造出任何类型和主题的对话&#xff1f;如果是这样&#xff0c;那么你需要了解一个简单而强大的工具&#xff0c;就是万能Prompt公式。 万能Promp…

微软office认证课程

感谢阅读 非原创声明&#xff0c;根据微软课程学习理解而已Get started with Microsoft 365Work Smarter with Microsoft Word与 Microsoft Word 更加智能地工作Microsoft 365 SubscriptionsMicrosoft 365 订阅个人补充Exercise: Finding Help and changing account settings练…

cpp 程序与 c 程序的相互调用(extern “C“ 的使用)

实际在编译的过程中&#xff0c;.cpp 文件调用 .c文件中的函数会出错。假设代码结构如下&#xff1a; 目录 一、编译过程分析 1、预处理 2、编译 3、汇编 4、链接 二、问题解决 1、解决方案 2、解决思路 一、编译过程分析 1、预处理 该阶段头文件会被展开&#xff0c…

FPGA学习笔记(二):时序逻辑之计数器

与组合逻辑&#xff08;给定输入&#xff0c;输出是确定的&#xff0c;与时间无关&#xff09;相比较&#xff0c;时序逻辑不仅仅与输入信号相关&#xff0c;还与时钟信号相关。 D触发器&#xff1a;在上升沿时&#xff08;CLK&#xff09;才将输出&#xff08;Q&#xff09;修…

Failed to load property source from location ‘classpath:/application.yaml‘

问题 项目起动时&#xff0c;找不到yaml&#xff0c;各种处理还是没有解决&#xff0c;比如&#xff0c;clean&#xff0c;重启电脑&#xff0c;检查utf8等。 同事拉的新代码又是正常可以跑&#xff0c;这就有点奇怪了。 11:07:39.682 [main] ERROR org.springframework.boot.…

【19】linux进阶——后台运行()和nohup命令

大家好&#xff0c;这里是天亮之前ict&#xff0c;本人网络工程大三在读小学生&#xff0c;拥有锐捷的ie和红帽的ce认证。每天更新一个linux进阶的小知识&#xff0c;希望能提高自己的技术的同时&#xff0c;也可以帮助到大家 另外其它专栏请关注&#xff1a; 锐捷数通实验&…

分享自己接私活常用的开源系统

目前开源系统是JNPF框架。技术栈上是SpringBoot、SpringCloud、Spring Web、MyBatis、Swagger、Vue、Element。 这些都是比较主流的技术&#xff0c;无论是技术层面的先进性还是学习难度都是比较低的&#xff0c;目前网络上有大量可供参考学习的资料。 并且它支持前后端分离和…

Ceph入门到精通-Ceph如何扩展到超过十亿个对象?

Ceph如何扩展到超过十亿个对象&#xff1f;-ceph部署多少个节点 (51cto.com) 越来越多的组织被要求管理数十亿个文件和几百上千PB的数据。无论是在公共云还是本地环境中&#xff0c;Ceph对象存储都是值得考虑的一个选项。本篇文章将通过七部分的精选内容为下面这些问题提供答案…