深度解析 Spring 源码:探秘 CGLIB 代理的奥秘

在这里插入图片描述

文章目录

    • 一、CGLIB 代理简介
      • 1.1 CGLIB 代理的基本原理和特点
      • 1.2 分析 CGLIB 如何通过字节码技术创建代理类
    • 二、深入分析 CglibAopProxy 类的结构
      • 2.1 CglibAopProxy 类结构
      • 2.2 CglibAopProxy 类源码
    • 三、CGLIB 代理对象的创建过程
      • 3.1 配置 Enhancer 生成代理对象
      • 3.2 探讨如何通过字节码生成技术嵌入拦截器逻辑到代理类中
    • 四、CGLIB 代理链的处理
      • 4.1 拦截器的调用顺序
      • 4.2 实现拦截器具体逻辑
    • 五、实践与应用
      • 5.1 编写自定义的 CGLIB 拦截器
      • 5.2 实现对非接口类的代理和增强功能

一、CGLIB 代理简介

1.1 CGLIB 代理的基本原理和特点

CGLIB是一个强大的、高性能的代码生成库。它被广泛应用于AOP(面向切面编程)、ORM(对象关系映射)和其他一些框架中。

CGLIB代理的基本原理

  1. 创建代理类:CGLIB通过ASM字节码操作框架,在运行时动态生成目标类的子类。这个子类会继承自目标类。
  2. 方法拦截:在生成的子类中,会覆盖所有非final的方法。覆盖的方法会委托给一个用户定义的拦截器(MethodInterceptor),拦截器中包含了增强的代码。
  3. 调用流程:当调用代理类的方法时,实际上是在调用被覆盖的方法。这些方法内部会调用拦截器,拦截器再去调用原始类的相应方法。

CGLIB代理的特点

  1. 无需接口:CGLIB代理不需要目标类实现任何接口,因为它是通过继承的方式来实现代理的。
  2. 性能:CGLIB生成的代理类是目标类的子类,相比于JDK动态代理(接口代理),CGLIB代理通常有更好的性能,因为它直接调用父类的方法,减少了反射调用的开销。
  3. 灵活性:由于CGLIB代理是通过继承实现的,它无法代理final类和方法。但是,它提供了比JDK代理更高的灵活性,因为它可以代理任何类,而不受接口限制。
  4. 复杂性:CGLIB代理的实现比JDK动态代理复杂,因为它涉及到字节码生成和类加载机制。
  5. 兼容性:CGLIB代理通常与Spring框架结合使用,Spring AOP默认使用JDK动态代理,但如果目标对象没有实现接口,Spring AOP会自动切换到CGLIB代理。

1.2 分析 CGLIB 如何通过字节码技术创建代理类

CGLIB通过操纵字节码,创建出目标类的子类,并在子类中覆盖非final的方法,从而实现方法拦截和增强。

CGLIB创建代理类的基本步骤

  1. 确定目标类:首先要确定需要被代理的目标类。CGLIB代理不需要目标类实现任何接口,因为它是通过继承的方式来实现代理的。
  2. 创建Enhancer对象EnhancerCGLIB中的一个核心类,用于创建代理类。首先创建一个Enhancer实例,并设置其父类(即目标类)。
  3. 设置CallbackCallback是一个接口,用于定义代理类中覆盖方法的逻辑。通常使用MethodInterceptor接口,它允许我们在调用原始方法之前和之后插入自定义代码。将实现的Callback对象设置给Enhancer
  4. 创建代理类:调用Enhancercreate()方法,CGLIB会使用ASM字节码操作框架来动态生成一个继承自目标类的子类。这个子类会覆盖所有非final的方法,并将调用委托给Callback对象。
  5. 使用代理类create()方法返回的是一个代理类的实例,这个实例可以被当作目标类的实例来使用。当调用代理类的方法时,实际上会调用MethodInterceptor中的intercept()方法。
  6. 方法调用流程:在intercept()方法中,可以调用Method对象的invoke()方法来执行原始方法。这样,我们就可以在原始方法执行前后插入自定义的逻辑,实现方法的拦截和增强。

二、深入分析 CglibAopProxy 类的结构

2.1 CglibAopProxy 类结构

  • 成员变量
    • AdvisedSupport advised:存储了AOP配置信息的数据结构,如目标对象、切面等。
    • Callback callback:CGLIB 回调对象,负责实现代理逻辑。
  • 构造方法
    • CglibAopProxy(AdvisedSupport config):构造方法接收一个 AdvisedSupport 参数,用于设置AOP配置信息。
  • 核心方法
    • getProxy(ClassLoader classLoader):生成代理对象的核心方法,接收一个 ClassLoader 参数用于加载代理类。
    • createProxyClassAndInstance(Enhancer enhancer, Callback[] callbacks):使用 CGLIB 的 Enhancer 创建代理类,并返回代理对象的实例。
    • proxy(ClassLoader classLoader, Callback[] callbacks):创建代理类并生成代理对象的实现逻辑, 使用 Enhancer 创建代理类,并指定 Callback 对象,完成代理类的生成和实例化。
    • createEnhancer():创建 Enhancer 对象,用于生成代理类, Enhancer 是 CGLIB 中负责生成代理类的核心类。

2.2 CglibAopProxy 类源码

仅展示部分源码,其它源码会在下方解决其它问题时会出现,没有出现的,读者感兴趣可以自行去解读源码。

在这里插入图片描述

三、CGLIB 代理对象的创建过程

代理对象的创建过程: 检查是否可以使用缓存的代理对象 -> 准备 CGLIB Enhancer -> 配置 Enhancer -> 设置回调处理器(Callback) -> 生成代理类字节码 -> 创建代理对象实例 -> 将代理对象缓存起来

3.1 配置 Enhancer 生成代理对象

Enhancer 对象通过调用 create() 方法来生成代理对象。

在这里插入图片描述

createHelper() 方法用来实际创建代理对象。

在这里插入图片描述

AbstractClassGenerator 对象通过调用 create() 方法,根据给定的键值(key)创建对象实例。

在这里插入图片描述

3.2 探讨如何通过字节码生成技术嵌入拦截器逻辑到代理类中

createProxyClassAndInstance 负责创建代理类的实例,使用 CGLIB 技术创建代理对象,并将指定的拦截器回调方法应用于代理对象上。

在这里插入图片描述

四、CGLIB 代理链的处理

通过ReflectiveMethodInvocation 类了解到在 Spring 框架中如何构建和执行代理链,以及拦截器如何在拦截器链中协作,以实现对目标方法的拦截和处理。

4.1 拦截器的调用顺序

在拦截器链中如何依次执行拦截器,并通过判断和调用不同的拦截器或目标方法来实现拦截和处理逻辑。

在这里插入图片描述

invokeJoinpoint()用于执行目标方法,如果拦截器链中已经没有下一个拦截器了,或者拦截器中的某个拦截器选择不继续执行拦截器链,那么就会调用这个方法来执行目标方法。

在这里插入图片描述

4.2 实现拦截器具体逻辑

定义了方法拦截器的标准,任何实现该接口的类都可以作为 Spring AOP 中的拦截器,用于在目标方法执行前后添加额外的逻辑。

在这里插入图片描述

五、实践与应用

5.1 编写自定义的 CGLIB 拦截器

假设有一个简单的服务类 UserService,其中包含一些方法,希望能够在调用这些方法之前和之后记录日志。使用CGLIB来实现一个拦截器,记录方法调用的开始和结束时间。

  1. 服务类 UserService,模拟创建和更新用户信息。
public class UserService {

    public void createUser(String username) {
        System.out.println("Creating user: " + username);
        // 模拟创建用户的逻辑
    }

    public void updateUser(String username) {
        System.out.println("Updating user: " + username);
        // 模拟更新用户的逻辑
    }
}
  1. 自定义的CGLIB拦截器,用于记录方法调用的开始和结束时间。
/**
 * 创建 LoggingInterceptor 类,实现 MethodInterceptor 接口
 */
public class LoggingInterceptor implements MethodInterceptor {
    
    /**
     * 参数:obj 是被代理的对象实例
     *      method 是被调用的方法对象
     *      args 是方法的参数数组
     *      proxy 是用于调用父类(被代理类)方法的代理对象
     */
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        // 获取方法调用开始时的时间戳
        long startTime = System.currentTimeMillis();
        System.out.println("Method " + method.getName() + " start at: " + startTime);

        // 调用被代理类的原始方法,而不是代理对象的方法,以避免循环调用
        Object result = proxy.invokeSuper(obj, args);

        // 获取方法调用结束时的时间戳
        long endTime = System.currentTimeMillis();
        System.out.println("Method " + method.getName() + " end at: " + endTime);
        // 方法执行所花费的时间
        System.out.println("Method " + method.getName() + " execution time: " + (endTime - startTime) + " milliseconds");

        // 调用原始方法后的返回值
        return result;
    }

    public static void main(String[] args) {
        UserService userService = new UserService();

        // 使用CGLIB的 Enhancer 类创建了 UserService 类的代理对象,并将拦截器设置为回调方法
        Enhancer enhancer = new Enhancer();
        // 设置了要代理的目标类是 UserService
        enhancer.setSuperclass(UserService.class);
        // 指定了在方法调用时应该执行的拦截逻辑
        enhancer.setCallback(new LoggingInterceptor());

        // 创建代理对象,将会在方法调用时执行我们定义的拦截逻辑
        UserService userServiceProxy = (UserService) enhancer.create();

        // 调用代理对象的 createUser 和 updateUser 方法来触发拦截器的拦截逻辑
        userServiceProxy.createUser("John Doe");
        userServiceProxy.updateUser("Jane Smith");
    }
}

//输出结果:
Method createUser start at: 1621802728000
Creating user: John Doe
Method createUser end at: 1621802728000
Method createUser execution time: 0 milliseconds
Method updateUser start at: 1621802728000
Updating user: Jane Smith
Method updateUser end at: 1621802728000
Method updateUser execution time: 0 milliseconds

5.2 实现对非接口类的代理和增强功能

实现对非接口类的代理和增强功能通常使用 Spring AOP来实现,提供了一种便捷的方式来在方法执行前、执行后、方法抛出异常时等时机插入特定逻辑,而无需修改原始类的代码。

假设有一个订单管理系统,其中包含一个 OrderService 类,该类负责处理订单相关的业务逻辑,比如创建订单、更新订单状态等。希望在处理订单相关业务时,记录日志并统计方法执行时间。

  1. 切面类 OrderAspect。
@Aspect
@Component
public class OrderAspect {

    /**
     * 切面方法,用于实现切面的逻辑 -> 表示正在执行目标方法之前
     * 接受一个 JoinPoint 参数,连接点 -> 被增强的目标方法
     */
    @Before("execution(* com.example.service.OrderService.*(..))")
    public void logBefore(JoinPoint joinPoint) {
        System.out.println("Before executing method: " + joinPoint.getSignature());
    }

     /**
     * 切面方法,用于实现切面的逻辑 -> 表示目标方法执行完成后
     * 接受一个 JoinPoint 参数,连接点 -> 被增强的目标方法
     */
    @After("execution(* com.example.service.OrderService.*(..))")
    public void logAfter(JoinPoint joinPoint) {
        System.out.println("After executing method: " + joinPoint.getSignature());
    }
}
  1. 配置类中启用 Spring AOP 功能。
/**
 * 标识这个类是一个配置类 -> 告诉 Spring 容器如何配置应用程序上下文
 * 启用了 AspectJ 自动代理 -> 告诉 Spring 在运行时生成 AOP 代理以支持 @AspectJ 切面
 * 指示 Spring 在包 com.example 及其子包中扫描组件 -> 自动发现并注册带有 @Component、@Service、@Repository 和 @Controller 注解的 bean
 */
@Configuration
@EnableAspectJAutoProxy
@ComponentScan(basePackages = "com.example")
public class AppConfig {
    
}
  1. OrderService 类。
/**
 * 调用 OrderService 类的 createOrder() 或 updateOrderStatus() 方法时,OrderAspect 切面中定义的增强逻辑会在方法执行前后生效,从而实现了对非接口类的代理和增强功能
 */
@Service
public class OrderService {

    public void createOrder() {
        // 模拟创建订单的业务逻辑
        System.out.println("Creating order...");
    }

    public void updateOrderStatus() {
        // 模拟更新订单状态的业务逻辑
        System.out.println("Updating order status...");
    }
}

// 输出结果:
Before executing method: public void com.example.service.OrderService.createOrder()
Creating order...
After executing method: public void com.example.service.OrderService.createOrder()

Before executing method: public void com.example.service.OrderService.updateOrderStatus()
Updating order status...
After executing method: public void com.example.service.OrderService.updateOrderStatus()

对乐于苦斗的人来说,苦斗不是憾事,而是乐事

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【斯坦福因果推断课程全集】1_随机对照试验1

目录 The average treatment effect Difference-in-means estimation IID Sampling and Population Asymptotics Example: The linear model Regression adjustments with a linear model 随机对照试验(RCT)是统计因果推论的基础。如果有的话&#…

Linux - 文件管理高级 sed

3.处理字符 sed ① sed 默认情况下不会修改原文件内容 ② sed 是一种非交互式的编辑器 3.1 工作原理 将原文件一行一行的进行处理,取出一行,放入“模式空间进行处理”,处理完成之后将结果输出到屏幕上,然后读取下一行&#xf…

瓦罗兰特国际服 外服游玩教程 瓦罗兰特外服下载注册游玩指南

瓦罗兰特国际服 外服游玩教程 瓦罗兰特外服下载注册游玩指南 瓦罗兰特作为当今游戏圈顶流的一款热门FPS。游戏,作为拳头游戏公司划时代的一款游戏。游戏不仅延续了传统FPS游戏的玩法,还添加许多新玩法,这也是游戏可以吸引大批量玩家的原因之…

vulnhub靶场之FunBox-10

一.环境搭建 1.靶场描述 As always, its a very easy box for beginners. This works better on VitualBox rather than VMware 2.靶场下载 Funbox: Under Construction! ~ VulnHub 3.靶场启动 靶场IP地址我们不知道,但是网段我们知道是192.168.2.0/24 二.信息…

Filter和ServletContext和Listener

目录 Filter案例 解决全站乱码问题 登录权限校验 ServletContext对象 Listener(监听器) Filter案例 解决全站乱码问题 我们每次访问每个servlet都要书写处理请求和响应乱码的代码,这样代码十分冗余,所以我们可以在过滤中 We…

python列表的进阶

小结: # 列表的删除小结: # 删除列表的最后一列 punished students.pop() print(被罚站的人是: punished ,同学们引以为戒。)# 根据下标删除 del students[0]#根据名称删除 students.remove(王熙凤)在今天的课程里&#xff0c…

k8s自定义资源你会创建吗

创建自定义资源定义 CustomResourceDefinition 当你创建新的 CustomResourceDefinition(CRD)时,Kubernetes API 服务器会为你所 指定的每一个版本生成一个 RESTful 的 资源路径。CRD 可以是名字空间作用域的,也可以是集群作用域的…

短剧系统源码:构建互动娱乐的新平台

随着数字媒体的兴起,短剧成为了一种新兴的娱乐形式,它以紧凑的叙事和快速的节奏迎合了现代观众的观看习惯。短剧系统源码的开发,为短剧内容的创作、传播和消费提供了一个全面的技术解决方案。本文将探讨短剧系统源码的关键组成部分及其功能。…

如何让 LightRoom 每次导入照片后不自动弹出 SD 卡 LR

如何让 LightRoom 每次导入照片后不自动弹出 SD 卡 LR 在导入窗口左上角有个选项: 导入后弹出 把这个去掉就可以了

FreeRTOS同步互斥与通信

本章简介: 本章是概述性的内容。可以把多任务系统当做一个团队,里面的每一个任务就相当于团队里的一个人。团队成员之间要协调工作进度(同步)、争用会议室(互斥)、沟通(通信)。多任务系统中所涉及的概念,都可以在现实生活中找到例子。 各类RT…

Transformer从0到1的学习【还有2-10,别想太多】

1.高纬度介绍Transformer 1.分为编码Encoders和解码器Decoders:“我爱你”作为编码器Encoders的输入进行编码得到序列码后,作为解码器的输入得到输出即为,“I Love you”。 2.编码器和译码器的具体拆分: 左边的编码器Encoders的…

(函数)判断一句话中最长的单词(C语言)

一、运行结果&#xff1b; 二、源代码&#xff1b; # define _CRT_SECURE_NO_WARNINGS # include <stdio.h>//声明函数&#xff1b; int aiphabetic(char); int longest(char[]);int main() {//初始化变量值&#xff1b;int i;char line[100] { 0 };//获取用户输入字符…

每天写两道(五)合并两个有序链表、最长回文子串

21.合并两个有序链表 . - 力扣&#xff08;LeetCode&#xff09; 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 示例 1&#xff1a; 输入&#xff1a;l1 [1,2,4], l2 [1,3,4] 输出&#xff1a;[1,1,2,3,4,4] (1)迭代法…

牛客网刷题 | BC105 菱形图案

目前主要分为三个专栏&#xff0c;后续还会添加&#xff1a; 专栏如下&#xff1a; C语言刷题解析 C语言系列文章 我的成长经历 感谢阅读&#xff01; 初来乍到&#xff0c;如有错误请指出&#xff0c;感谢&#xff01; 描述 KiKi学习了循环&am…

VMware Workstation中WinXP联网问题

我一直以为我的虚拟机上的XP没有联网 因为 蒙了半天&#xff0c;发现是因为这个网址打不开&#xff0c;不是没有网 太傻了 不如在cmd命令行中通过ping baidu.com来判断是否联网

Vue插槽与作用域插槽

title: Vue插槽与作用域插槽 date: 2024/6/1 下午9:07:52 updated: 2024/6/1 下午9:07:52 categories: 前端开发 tags:VueSlotScopeSlot组件通信Vue2/3插槽作用域API动态插槽插槽优化 第1章&#xff1a;插槽的概念与原理 插槽的定义 在Vue.js中&#xff0c;插槽&#xff08;…

【多目标跟踪】《FlowMOT: 3D Multi-Object Tracking by Scene Flow Association》论文阅读笔记

0.论文 论文地址链接:https://arxiv.org/pdf/2012.07541v1 通过流的方式跟踪是一个比较新颖的点,所以这里比较关注运动跟踪,是如果做到流的跟踪来预测目标的位置以及ID绑定的。 FlowMOT的框架结构如下所示,本中会主要关注下运动跟踪、数据关联、ID分配、新生/消亡…

cmd窗口输出内容乱码问题

出现这样的问题是因为编码格式和解码格式不一样导致的&#xff0c;cmd窗口的默认解码格式为GBK&#xff0c;如想修改cmd默认编码格式可以按照下面步骤操作&#xff1a;打开cmd窗口输入&#xff1a;chcp 65001 65001指的是utf-8编码如果不清楚编码对应的 页面编码是是多少&#…

基于51单片机和NRF24L01的无线温度监控设计

一、设计功能 由单片机、温度传感器、无线模块NRF24L01以及液晶显示器等构成高精度远 程无线温度监测系统。 温度显示精确到小数点后一位。 按键设定过温值&#xff0c;过温在液晶屏提示。 系统设计 三、器件选择3.1温度信号采集模块 传统的温度检测大多以热敏电阻为传感器&a…

4K高刷显示器 - 蚂蚁电竞ANT27VU

可以毫不夸张地说&#xff0c;每一局游戏最终能够取得胜利&#xff0c;实际上都与一套极为优秀的电竞 PC 有着紧密的关联&#xff0c;因为其能够提供强大的性能支持与流畅的体验。同样的道理&#xff0c;一套优秀的电竞 PC 若想发挥出最佳的效果&#xff0c;那也都离不开一台能…