【深度思考】聊聊CGLIB动态代理原理

1. 简介

CGLIB的全称是:Code Generation Library。

CGLIB是一个强大的、高性能、高质量的代码生成类库,它可以在运行期扩展Java类与实现Java接口,

底层使用的是字节码处理框架ASM。

Github地址:https://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();
    }
}

运行以上代码,效果如下图所示:

【深度思考】聊聊CGLIB动态代理原理

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

3. 原理

看下上面的测试类代码,首先是创建了一个net.sf.cglib.proxy.Enhancer对象,然后调用了setSuperclass()方法

将enhancer对象的父类设置为Coder类:

【深度思考】聊聊CGLIB动态代理原理

紧接着调用了setCallback()方法将enhancer对象的方法拦截器设置为自定义的AttendanceMethodInterceptor:

【深度思考】聊聊CGLIB动态代理原理

【深度思考】聊聊CGLIB动态代理原理

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

先打印下,简单看下这个代理类的信息:

【深度思考】聊聊CGLIB动态代理原理

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

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

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

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

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

【深度思考】聊聊CGLIB动态代理原理

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

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

【深度思考】聊聊CGLIB动态代理原理

可以看出Coder$$EnhancerByCGLIB$$8e91f654.class继承了Coder类(也就是说自动生成的代理类其实是被代理类的一个子类),

并且重写了Coder类的work()方法,重写后的work()方法会调用自定义的方法拦截器AttendanceMethodInterceptor里的intercept()

方法。

然后看下Coder$$EnhancerByCGLIB$$8e91f654$$FastClassByCGLIB$$4e5eb5aa,从名称上可以看出这个类的前半段和上面的类的名称

是一样的,后半段拼接上了$$FastClassByCGLIB$$4e5eb5aa,从功能上说,这个类是上面的代理类的索引类,重点关注下里面的

getIndex()方法和invoke()方法:

【深度思考】聊聊CGLIB动态代理原理

最后看下Coder$$FastClassByCGLIB$$398819d0,这个类是被代理类Coder的索引类,重点也是关注下里面的

getIndex()方法和invoke()方法:

【深度思考】聊聊CGLIB动态代理原理

知道了这3个类的作用后,再一步一步看下示例代码中coder.work();的调用过程,因为coder是生成的代理类的实例,所以

coder.work();首先调用的是Coder$$EnhancerByCGLIB$$8e91f654的work()方法:

【深度思考】聊聊CGLIB动态代理原理

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

【深度思考】聊聊CGLIB动态代理原理

然后看下invokeSuper()方法:

【深度思考】聊聊CGLIB动态代理原理

首先执行的是init()方法,在该方法内部对fastClassInfo字段进行了赋值:

【深度思考】聊聊CGLIB动态代理原理

从上图可以看出,fci.f1是自动生成的Coder类的索引类Coder$$FastClassByCGLIB$$398819d0,所以fci.i1 = fci.f1.getIndex(sig1);

其实执行的是的Coder$$FastClassByCGLIB$$398819d0getIndex()方法:

【深度思考】聊聊CGLIB动态代理原理

fci.f2是自动生成的代理类的索引类Coder$$EnhancerByCGLIB$$8e91f654$$FastClassByCGLIB$$4e5eb5aa

所以fci.i2 = fci.f2.getIndex(sig2);其实执行的是的Coder$$EnhancerByCGLIB$$8e91f654$$FastClassByCGLIB$$4e5eb5aa

getIndex()方法:

【深度思考】聊聊CGLIB动态代理原理

看完init()方法后再回到invokeSuper()方法:

【深度思考】聊聊CGLIB动态代理原理

上图中的FastClassInfo fci = fastClassInfo;使用到的字段fastClassInfo在init()方法内部已经赋过值,

fci.f2其实是自动生成的代理类的索引类Coder$$EnhancerByCGLIB$$8e91f654$$FastClassByCGLIB$$4e5eb5aa

fci.i2值是1,

所以fci.f2.invoke(fci.i2, obj, args);实际执行的是:

【深度思考】聊聊CGLIB动态代理原理

这里的var10000其实是自动生成的代理类Coder$$EnhancerByCGLIB$$8e91f654的实例,所以接着调用的是

代理类Coder$$EnhancerByCGLIB$$8e91f654CGLIB$work$0()方法:

【深度思考】聊聊CGLIB动态代理原理

这里的super指的是Coder类,所以super.work();实际执行的是Coder类的work()方法:

【深度思考】聊聊CGLIB动态代理原理

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

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

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

关于JDK动态代理,可以查看上一篇博客:【深度思考】聊聊JDK动态代理原理

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

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

    原因分析:

    JDK动态代理生成的代理类继承了java.lang.reflect.Proxy,因为Java是单继承的,如果不通过实现接口的形式,

    无法对类进行扩展。

    CGLIB动态代理生成的代理类实际上是被代理类的子类,所以被代理类可以不实现接口。

  2. 自动生成类的数量不同

    JDK动态代理只会生成1个代理类,一般情况下名称为:com.sun.proxy.$Proxy0

    CGLIB动态代理会生成好几个类,核心的3个分别是:

    1)代理类:被代理类的子类,名称格式为Coder$$EnhancerByCGLIB$$8e91f654,包名和被代理类包名一致。

    2)代理类的索引类:名称格式为Coder$$EnhancerByCGLIB$$8e91f654$$FastClassByCGLIB$$4e5eb5aa

    包名和被代理类包名一致。

    3)被代理类的索引类:名称格式为Coder$$FastClassByCGLIB$$398819d0,包名和被代理类包名一致。

  3. 生成代理类技术不同

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

    CGLIB动态代理使用ASM框架生成字节码文件。

  4. 调用方式不同

    JDK动态代理:代理类--->InvocationHandler.invoke()--->被代理类方法(用到了反射)。

    CGLIB动态代理:代理类--->MethodInterceptor.intercept()--->代理类索引类getIndex()--->

    代理类索引类invoke()--->代理类--->被代理类。(直接调用)

文章持续更新,欢迎关注微信公众号「申城异乡人」第一时间阅读!

原文链接:https://www.cnblogs.com/zwwhnly/p/17340026.html

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:【深度思考】聊聊CGLIB动态代理原理 - Python技术站

(0)
上一篇 2023年4月22日
下一篇 2023年4月22日

相关文章

  • Spring mvc文件上传下载代码实例

    Spring MVC文件上传下载代码实例 在Web应用程序中,文件上传和下载是常见的功能。Spring MVC提供了方便的API来处理文件上传和下载。本文将介绍如何在Spring MVC中实现文件上传和下载,并提供两个示例说明。 文件上传 步骤一:配置文件上传 首先,我们需要在spring-servlet.xml文件中配置文件上传。可以通过添加以下配置来实现…

    Java 2023年5月17日
    00
  • 员工管理系统java版

    这里为您详细讲解“员工管理系统java版”的完整攻略。 系统概述 这是一个基于Java语言开发的员工管理系统,主要功能包括员工信息录入、查询、修改和删除,部门信息录入、查询和修改,以及用户权限管理等。 系统环境 本系统运行需要JDK1.8及以上版本,建议使用IntelliJ IDEA等集成开发环境进行开发和调试。 数据库设计 本系统使用MySQL数据库进行数…

    Java 2023年5月23日
    00
  • 详解Java的位操作符

    详解Java的位操作符 在Java编程中,位操作符是十分重要的操作符之一。它可以对数字进行位运算,通过改变二进制数的位来实现一些比较复杂的操作。本文将详细讲解Java的位操作符。 按位与(&)操作符 按位与操作符”&”主要用于对二进制数进行与运算。如果两个位都是1,那么结果就是1,否则结果就是0。下面是一个示例: int a = 6; int…

    Java 2023年5月26日
    00
  • 整理的比较全的一句话后门代码(方面大家查找后门)

    如何查找后门: 首先,要清楚什么是后门代码。后门代码是指程序员为了方便自己的管理而在程序中设置的留口,可以快速地绕过正常的登录验证方式,对系统的安全造成威胁。一些常见后门代码的特征包括容易被搜索的字符序列,包含明显的登录验证过程,并且能与一个远程服务器进行通信等。 在代码中搜索常用的后门代码字符串。一些常见的后门代码包括“eval”,“base64_deco…

    Java 2023年6月15日
    00
  • SpringMVC上传文件的两种方法

    在 Spring MVC 中,我们可以使用两种方法来上传文件,分别是使用 MultipartFile 类型的参数和使用 CommonsMultipartResolver 类。本文将详细讲解 Spring MVC 上传文件的两种方法,包括如何使用 MultipartFile 类型的参数和如何使用 CommonsMultipartResolver 类,并提供两个…

    Java 2023年5月18日
    00
  • 如何测试Spring MVC应用

    以下是关于“如何测试Spring MVC应用”的完整攻略,其中包含两个示例。 1. 前言 Spring MVC是一种基于Java的Web框架,它提供了一种灵活的方式来开发Web应用程序。在开发Spring MVC应用程序时,测试是非常重要的一部分。本攻略将详细讲解如何测试Spring MVC应用程序。 2. 测试Spring MVC应用程序的方法 Sprin…

    Java 2023年5月16日
    00
  • Spring Boot如何实现定时任务的动态增删启停详解

    下面我会详细讲解“Spring Boot如何实现定时任务的动态增删启停详解”的完整攻略。 什么是定时任务? 定时任务(Scheduled Task)是指在指定的时间点或时间间隔内自动执行某个操作的任务。在很多场景下,我们经常需要定时执行某些操作,例如定时清理临时数据、定时发送邮件等。 Spring Boot如何实现定时任务 Spring Boot 提供了标准…

    Java 2023年5月19日
    00
  • 图文演示Flash+ASP实现用户登录/注册程序第1/2页

    下面我将详细讲解“图文演示Flash+ASP实现用户登录/注册程序第1/2页”的完整攻略。 一、前置知识 在学习本文之前,你需要了解以下知识: Flash基础知识:包括Flash界面结构、基本操作、动画制作等。 ASP基础知识:包括ASP排版、变量定义、数据类型、循环控制、函数封装等。 如果你缺乏上述知识,请自行学习补充,并确保已经掌握了这些知识点。 二、实…

    Java 2023年6月15日
    00
合作推广
合作推广
分享本页
返回顶部