当前位置: 首页 > news >正文

15. Spring AOP 的实现原理 代理模式

目录

1. 代理模式

2. 静态代理 

3. 动态代理

3.1 JDK 动态代理

3.2 CGLIB 动态代理

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

5. Spring代理选择

6. Spring AOP 实现原理

6.1 织入

7. JDK 动态代理实现 

8. CGLIB 动态代理实现

9. 总结


1. 代理模式

代理模式:为其他对象提供⼀种 代理 以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另⼀个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。

代理模式分为静态代理动态代理。 

2. 静态代理 

静态代理中,我们对目标对象的每个方法的增强都是手动完成的,非常不灵活(比如接口⼀旦新增加方法,目标对象和代理对象都要进行修改)且麻烦(需要对每个目标类都单独写⼀个代理类)。 实际应用场景非常少,日常开发几乎看不到使用静态代理的场景。
上面我们是从实现和应用角度来说的静态代理,从 JVM 层面来说, 静态代理在编译时就将接⼝、实现类、代理类这些都变成了⼀个个实际的 class 文件

静态代理实现步骤:

  1. 定义⼀个接口及其实现类;
  2. 创建⼀个代理类同样 实现这个接口;
  3. 将目标对象注入进代理类,然后在代理类的对应方法调用目标类中的对应方法。

这样,我们就可以通过代理类屏蔽目标对象的访问,并且可以在目标方法执行前后实现其他的功能。接下来,我们来看一下具体是如何实现静态代理的:

1. 定义接口

2. 实现接口

3. 创建代理类并同样实现支付接口

4. 实际使用

3. 动态代理

相比于静态代理来说,动态代理更加灵活。我们不需要针对每个目标类都单独创建一个代理类,并且也不需要我们必须实现接口,我们可以直接代理实现类( CGLIB 动态代理机制)。

从 JVM 角度来说,动态代理是在运行时动态生成类字节码,并加载到 JVM 中 的。
Spring AOP 的实现依赖了动态代理。
就 Java 来说,动态代理的实现方式有很多种,比如 JDK 动态代理CGLIB 动态代理等等。

3.1 JDK 动态代理

在 Java 动态代理机制中 InvocationHandler 接口 Proxy 类 是核心。
JDK 动态代理类步骤:
  1. 定义⼀个接口及其实现类;
  2. 自定义 InvocationHandler 并重写 invoke 方法 ,在 invoke 方法中会调用原生方法(被代理类的方法)并自定义⼀些处理逻辑;
  3. 通过 Proxy.newProxyInstance(ClassLoader loader,Class<?>[]
    interfaces,InvocationHandler h) 方法创建代理对象;

定义 JDK 动态代理类:

创建一个代理对象并使用:

Proxy 类中使用频率最高的方法是:newProxyInstance() ,这个方法主要用来生成⼀个代理对象。

这个方法⼀共有 3 个参数:
  1.  loader :类加载器,用于加载代理对象。
  2.  interfaces : 被代理类实现的⼀些接口;
  3.  h : 实现了 InvocationHandler 接口的对象; 

3.2 CGLIB 动态代理

JDK 动态代理有⼀个最致命的问题是其只能代理实现了接口的类。

 因此,可以通过 CGLIB 动态代理机制来避免。

CGLIB(Code Generation Library)是⼀个基于 ASM 的字节码生成库,它允许我们在运行时对字节码进行修改和动态生成。CGLIB 通过继承方式实现代理。很多知 名的开源框架都使用到 CGLIB,例如 Spring 的 AOP 模块中:如果目标对象 实现了接口 ,则默认采用 JDK 动态代理,否则采用 CGLIB 动态代理
在 CGLIB 动态代理机制中 MethodInterceptor 接口 Enhancer 类 是核心。

CGLIB 动态代理类使用步骤:

  1. 定义⼀个类;
  2. ⾃定义 MethodInterceptor 并重写 intercept 方法,intercept 用于拦截增强被代理类的方法,和 JDK 动态代理中的 invoke 方法类似;
  3. 通过 Enhancer 类的 create()创建代理类。

添加依赖:

和JDK 动态代理不同, CGLIB(Code Generation Library) 实际是属于⼀个开源项目,使用时需要手动添加相关依赖。

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

定义 MethodInterceptor(方法拦截器):

创建代理类, 并使用:

自定义 MethodInterceptor 并重写 intercept 方法, intercept 用于拦截增强被代理类的方法

  1. obj : 被代理的对象(需要增强的对象)
  2. method : 被拦截的方法(需要增强的方法)
  3. args : 方法入参
  4. proxy : 用于调用原始方法

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

  1. JDK 动态代理只能代理实现了接口的类或者直接代理接口,而 CGLIB 可以代理未实现任何接口的类。
  2. CGLIB 动态代理是通过生成⼀个被代理类的子类来拦截被代理类的方法调用,因此不能代理声明为 final。
性能: 大 部分情况都是 JDK 动态代理更优秀,随着 JDK 版本的升级,这个优势更加明显。

5. Spring代理选择

  1. proxyTargetClass 为 false, 目标实现了接口, 用 jdk 代理
  2. proxyTargetClass 为 false, 目标未实现接口, 用 cglib代理
  3. proxyTargetClass 为 true, 用 cglib 代理

6. Spring AOP 实现原理

Spring AOP 是构建在动态代理基础上,因此 Spring 对 AOP 的支持局限于方法级别的拦截。
Spring AOP 支持 JDK Proxy 和 CGLIB 方式实现动态代理。默认情况下, 实现了接口的类,使
用 AOP 会基于 JDK 生成代理类;没有实现接口的类,会基于 CGLIB 生成代理类。

6.1 织入

织入是把切面应用到目标对象并创建新的代理对象的过程,切面在指定的连接点被织入到目标对
象中。
在目标对象的生命周期里有多个点可以进行织入:
  • 编译期:切⾯在⽬标类编译时被织⼊。这种⽅式需要特殊的编译器。AspectJ的织⼊编译器就
    是以这种⽅式织⼊切⾯的。
  • 类加载期:切⾯在⽬标类加载到JVM时被织⼊。这种⽅式需要特殊的类加载器 (ClassLoader),它可以在⽬标类被引⼊应⽤之前增强该⽬标类的字节码。AspectJ5的加载 时织⼊(load-time weaving. LTW)就⽀持以这种⽅式织⼊切⾯。
  • 运行期:切面在应用运行的某一时刻被织入。一般情况下,在织入切面时,AOP 容器会为目标对象动态创建一个代理对象。Spring AOP 就是以这种方式织入切面的。

7. JDK 动态代理实现 

public interface PayService {void pay();
}
public class AliPayService implements PayService{@Overridepublic void pay(){System.out.println("ali pay...");}
}
public class PayServiceJDKInvocationHandler implements InvocationHandler {// 目标对象就是被代理对象private Object target;public PayServiceJDKInvocationHandler(Object target) {this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("安全检查");System.out.println("记录日志");System.out.println("记录开始时间");// 通过反射调用被代理的方法Object retVal = method.invoke(target,args);System.out.println("记录结束时间");return retVal;}public static void main(String[] args) {PayService target = new AliPayService();// 方法调用处理器InvocationHandler handler = new PayServiceJDKInvocationHandler(target);// 创建一个代理类:通过被代理类、被代理实现的接口、方法调用处理器来创建PayService proxy =(PayService) Proxy.newProxyInstance(target.getClass().getClassLoader(),new Class[]{PayService.class},handler);proxy.pay();}
}

8. CGLIB 动态代理实现

public interface PayService {void pay();
}
public class PayServiceCGLIBInterceptor implements MethodInterceptor {// 被代理对象private Object target;public PayServiceCGLIBInterceptor(Object target) {this.target = target;}@Overridepublic Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {System.out.println("安全检查");System.out.println("记录日志");System.out.println("记录开始时间");// 通过反射调用被代理的方法Object retVal = methodProxy.invoke(target,args);System.out.println("记录结束时间");return retVal;}public static void main(String[] args) {PayService target = new AliPayService();PayService proxy =(PayService) Enhancer.create(target.getClass(),new PayServiceCGLIBInterceptor(target));proxy.pay();}
}

9. 总结

AOP 是对某方面能力的统⼀实现,它是⼀种实现思想,Spring AOP 是对 AOP 的具体实现,Spring AOP 可通过 AspectJ(注解)的方式来实现 AOP 的功能,Spring AOP 的实现步骤是:
  1. 添加 AOP 框架支持;
  2. 定义切面和切点;
  3. 定义通知。
Spring AOP 是通过动态代理的方式,在运行期将 AOP 代码织入到程序中的,它的实现方式有两种: JDK Proxy 和 CGLIB。

相关文章:

15. Spring AOP 的实现原理 代理模式

目录 1. 代理模式 2. 静态代理 3. 动态代理 3.1 JDK 动态代理 3.2 CGLIB 动态代理 4. JDK 动态代理和 CGLIB 动态代理对比 5. Spring代理选择 6. Spring AOP 实现原理 6.1 织入 7. JDK 动态代理实现 8. CGLIB 动态代理实现 9. 总结 1. 代理模式 代理模式&#xf…...

死锁产生的原因以及解决方案

一.原因: 1.使用互斥锁. 2.除非主动释放,负责不能被抢占. 3.占用一把锁不释放,等待其它锁资源(保持现状). 4.锁形成环路. 二.解决方案: 给锁编号,上锁的时候从小到大依次上锁,譬如如果一个线程要上1号和2号两把锁,如果1号锁被占用,不能上2号锁,等其它线程释放1号锁资源后…...

【构造】CF1758 D

Problem - D - Codeforces 题意&#xff1a; 思路&#xff1a; 如果需要构造一个和为定值的序列&#xff0c;那么考虑n-d,n-d1,.....nd-1,nd这种形式 如果要保证不能重复&#xff0c;那么先考虑一个排列&#xff0c;然后在排列上操作 如果根据小数据构造出了一些简单情形&a…...

【腾讯云 Cloud Studio 实战训练营】永不宕机的IDE,Coding Everywhere

【腾讯云 Cloud Studio 实战训练营】永不宕机的IDE&#xff0c;随时随地写代码&#xff01; 写在最前视频讲解&#xff1a;Cloud Studio活动简介何为腾讯云 Cloud Studio?Cloud Studio简介免费试用&#xff0c;上手无忧Cloud Studio 特点及优势云端开发多种预制环境可选metawo…...

JavaScript将一层级对象数组转为children嵌套的三层级树状对象数组(多级树状分类)

有时候后端返回的数据不适合前端,我们就需要进行转换,比如我想用elementUI的级联选择器,而这个组件对数据格式有要求,本篇文章将介绍如何将一层级对象数组数据格式转为三层级嵌套children数组,JavaScript、Vue、小程序等都适用,使用情景为多级分类,嵌套数据 情况1:原数…...

Windows脚本启动Redis、Java和Nginx服务指南

文章目录 1. 完整的批处理脚本2. Redis服务3. Java服务4. Nginx服务 1. 完整的批处理脚本 echo offcd C:\path\to\redis tasklist /FI "IMAGENAME eq redis-server.exe" 2>NUL | find /I /N "redis-server.exe">NUL if "%ERRORLEVEL%"&qu…...

【宝藏系列】STM32之C语言基础知识

【宝藏系列】STM32之C语言基础知识 文章目录 【宝藏系列】STM32之C语言基础知识1️⃣位操作2️⃣define宏定义3️⃣ifdef条件编译4️⃣extern变量声明5️⃣typedef类型别名 C语言是单片机开发中的必备基础知识&#xff0c;本文列举了部分 STM32 学习中比较常见的一些C语言基础知…...

探索自除数:发现区间内的神奇数字

本篇博客会讲解力扣“728. 自除数”的解题思路&#xff0c;这是题目链接。 对于给定的正整数num&#xff0c;我们如何判断它是不是自除数呢&#xff1f;根据定义&#xff0c;我们只需要把num的每一位数字都取出来&#xff0c;判断能不能整除num&#xff0c;如果发现num的某一位…...

打卡力扣题目四

#左耳听风 ARST 打卡活动重启# 目录 一、题目 二、解题代码 三、解题思路 关于 ARTS 的释义 —— 每周完成一个 ARTS&#xff1a; ● Algorithm: 每周至少做一个 LeetCode 的算法题 ● Review: 阅读并点评至少一篇英文技术文章 ● Tips: 学习至少一个技术技巧 ● Share: 分享…...

npm yarn nrm

npm 和 yarn npm和yarn都是包管理器&#xff0c;yarn是在2016年发布的&#xff0c;那时npm还处于V3时期&#xff0c;那时候还没有package-lock.json文件&#xff0c;不稳定性、安装速度慢等缺点经常会受到广大开发者吐槽。此时&#xff0c;yarn 诞生了。yarn 的优点&#xff0c…...

关于我对刚开始学Java的小白想分享的内容:

编程是很有魅力的&#xff0c;让很多人为之痴迷 如果你是初学者&#xff0c;俗称小白&#xff0c;不妨看看下述内容&#xff1a; 文章目录 1. Java 简介 Java 是一门编程语言&#xff0c;发展至今已经成为一门真正意义上的语言标准。 Java是一个完整的平台&#xff0c;有一个…...

Redis学习路线(5)—— Redis生成唯一ID

一、全局唯一ID &#xff08;一&#xff09;在用户抢购时&#xff0c;就会生成订单并保存到数据库中&#xff0c;而订单表如果使用自增ID就会存在以下几种情况&#xff1a; 自增ID规律性太强受单表数据量的限制 &#xff08;二&#xff09;全局ID生成器&#xff0c;是一种在…...

django后台系统Tyadmin

无意之间发现个django的后台管理框架&#xff0c;仔细与xadmin对比了一下&#xff0c;无论是功能上还是便携性上都与xadmin特别相似&#xff0c;但个人感觉Tyadmin略胜一筹&#xff0c;因为外观上要比xadmin要美观&#xff0c;而且相比起来速度也快&#xff0c;部署甚至也和简单…...

设计模式适合用于解决特定的软件设计问题呢

当我们在开发软件时&#xff0c;经常会遇到各种各样的问题和挑战&#xff0c;例如如何处理对象之间的关系、如何实现复杂的业务逻辑、如何处理并发访问等。这些问题都是软件设计中经常遇到的问题&#xff0c;而设计模式就是为了解决这些问题而诞生的。 以下是一些常见的软件设…...

测试|测试分类

测试|测试分类 文章目录 测试|测试分类1.按照测试对象分类&#xff08;部分掌握&#xff09;2.是否查看代码&#xff1a;黑盒、白盒灰盒测试3.按开发阶段分&#xff1a;单元、集成、系统及验收测试4.按实施组织分&#xff1a;α、β、第三方测试5.按是否运行代码&#xff1a;静…...

矩阵中的路径(JS)

矩阵中的路径 题目 给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 单词必须按照字母顺序&#xff0c;通过相邻的单元格内的字母构成&#xff0c;其中“相邻”单元格是…...

Linux时间体系与LinuxPTP

Linux时间体系 Linux 需要提供“知道当前时间、计算时间长度、定时提醒”这三种功能。 其中知道当前时间和计算时间长度在某种程度上可以互相转换。即以UNIX Epoch计时开始可以知道当前时间。 一般硬件可以提供下列的硬件时钟&#xff1a; RTC 【真实时钟】 对于PC而言&…...

最优除法(力扣)数学 JAVA

给定一正整数数组 nums&#xff0c;nums 中的相邻整数将进行浮点除法。例如&#xff0c; [2,3,4] -> 2 / 3 / 4 。 例如&#xff0c;nums [2,3,4]&#xff0c;我们将求表达式的值 “2/3/4”。 但是&#xff0c;你可以在任意位置添加任意数目的括号&#xff0c;来改变算数的…...

Git代码管理

目录&#xff1a; git环境配置 git工作流程git常用命令gitlab实战gitlog分析与检索分支管理策略git合并与冲突 1.git环境配置 Git 简介&#xff1a; Git 是目前世界上最先进的分布式版本控制系统。Git 优点&#xff1a; 适合分布式开发&#xff0c;强调个体…...

使用vscode进行远程开发服务器配置

1.下载vscode 2.给vscode 安装python 和 remote ssh插件 remote—SSH扩展允许您使用任何具有SSH服务器的远程机器作为您的开发环境。 3.安装remote-SSH插件之后&#xff0c;vscode左侧出现电脑图标&#xff0c;即为远程服务&#xff0c;按图依次点击&#xff0c;进行服务器配置…...

基于距离变化能量开销动态调整的WSN低功耗拓扑控制开销算法matlab仿真

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.算法仿真参数 5.算法理论概述 6.参考文献 7.完整程序 1.程序功能描述 通过动态调整节点通信的能量开销&#xff0c;平衡网络负载&#xff0c;延长WSN生命周期。具体通过建立基于距离的能量消耗模型&am…...

STM32F4基本定时器使用和原理详解

STM32F4基本定时器使用和原理详解 前言如何确定定时器挂载在哪条时钟线上配置及使用方法参数配置PrescalerCounter ModeCounter Periodauto-reload preloadTrigger Event Selection 中断配置生成的代码及使用方法初始化代码基本定时器触发DCA或者ADC的代码讲解中断代码定时启动…...

JVM垃圾回收机制全解析

Java虚拟机&#xff08;JVM&#xff09;中的垃圾收集器&#xff08;Garbage Collector&#xff0c;简称GC&#xff09;是用于自动管理内存的机制。它负责识别和清除不再被程序使用的对象&#xff0c;从而释放内存空间&#xff0c;避免内存泄漏和内存溢出等问题。垃圾收集器在Ja…...

转转集团旗下首家二手多品类循环仓店“超级转转”开业

6月9日&#xff0c;国内领先的循环经济企业转转集团旗下首家二手多品类循环仓店“超级转转”正式开业。 转转集团创始人兼CEO黄炜、转转循环时尚发起人朱珠、转转集团COO兼红布林CEO胡伟琨、王府井集团副总裁祝捷等出席了开业剪彩仪式。 据「TMT星球」了解&#xff0c;“超级…...

DIY|Mac 搭建 ESP-IDF 开发环境及编译小智 AI

前一阵子在百度 AI 开发者大会上&#xff0c;看到基于小智 AI DIY 玩具的演示&#xff0c;感觉有点意思&#xff0c;想着自己也来试试。 如果只是想烧录现成的固件&#xff0c;乐鑫官方除了提供了 Windows 版本的 Flash 下载工具 之外&#xff0c;还提供了基于网页版的 ESP LA…...

selenium学习实战【Python爬虫】

selenium学习实战【Python爬虫】 文章目录 selenium学习实战【Python爬虫】一、声明二、学习目标三、安装依赖3.1 安装selenium库3.2 安装浏览器驱动3.2.1 查看Edge版本3.2.2 驱动安装 四、代码讲解4.1 配置浏览器4.2 加载更多4.3 寻找内容4.4 完整代码 五、报告文件爬取5.1 提…...

MFC 抛体运动模拟:常见问题解决与界面美化

在 MFC 中开发抛体运动模拟程序时,我们常遇到 轨迹残留、无效刷新、视觉单调、物理逻辑瑕疵 等问题。本文将针对这些痛点,详细解析原因并提供解决方案,同时兼顾界面美化,让模拟效果更专业、更高效。 问题一:历史轨迹与小球残影残留 现象 小球运动后,历史位置的 “残影”…...

JavaScript 数据类型详解

JavaScript 数据类型详解 JavaScript 数据类型分为 原始类型&#xff08;Primitive&#xff09; 和 对象类型&#xff08;Object&#xff09; 两大类&#xff0c;共 8 种&#xff08;ES11&#xff09;&#xff1a; 一、原始类型&#xff08;7种&#xff09; 1. undefined 定…...

MySQL:分区的基本使用

目录 一、什么是分区二、有什么作用三、分类四、创建分区五、删除分区 一、什么是分区 MySQL 分区&#xff08;Partitioning&#xff09;是一种将单张表的数据逻辑上拆分成多个物理部分的技术。这些物理部分&#xff08;分区&#xff09;可以独立存储、管理和优化&#xff0c;…...

解析“道作为序位生成器”的核心原理

解析“道作为序位生成器”的核心原理 以下完整展开道函数的零点调控机制&#xff0c;重点解析"道作为序位生成器"的核心原理与实现框架&#xff1a; 一、道函数的零点调控机制 1. 道作为序位生成器 道在认知坐标系$(x_{\text{物}}, y_{\text{意}}, z_{\text{文}}…...