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

【Spring】Spring AOP 初识及实现原理解析

  • 博主简介:想进大厂的打工人
  • 博主主页:@xyk:
  • 所属专栏: JavaEE进阶 

目录

文章目录

一、初识AOP

1.1 什么是AOP?

1.2 AOP的组成

1.2.1 切面(Aspect)

1.2.2 切点(Pointcut)

1.2.3 连接点(Join Point)

1.2.4 通知(Advice)

1.3 AOP的使用场景

二、Srping AOP 实现

2.1 添加Spring AOP 依赖

2.2 定义切面和切点

2.3 定义通知

三、Spring AOP 实现原理

3.1 什么是动态代理?

3.2 JDK 动态代理实现

3.3 CGLIB 动态代理实现

3.4 JDK 和 CGLIB 实现的区别


一、初识AOP

1.1 什么是AOP?

AOP(Aspect Oriented Programming):面向切面编程,它是⼀种思想,它是对某⼀类事情的
集中处理。
在我们想要对某一件事情进行集中处理,就可以使用到AOP,它提供一种将程序中的横切关注点模块化的方式。在 AOP 中,我们将这些横切关注点称为“切面”,它们独立于业务逻辑模块,但是可以在程序运行的不同阶段被织入到业务逻辑中。

简单来说,AOP 就是对某一件事进行集中处理的思想方式~

1.2 AOP的组成

1.2.1 切面(Aspect)

切⾯(Aspect)由切点(Pointcut)和通知(Advice)组成,它既包含了横切逻辑的定义,也包
括了连接点的定义。相当于处理某方面具体问题的一个类,包含多个方法,而这些方法就是切点和通知。

1.2.2 切点(Pointcut)

Pointcut 的作⽤就是提供⼀组规则来匹配连接点(Join Point),给满足规则的连接点添加通知(Advice),可以理解为用来进行主动拦截的规则(配置)

1.2.3 连接点(Join Point)

应⽤执⾏过程中能够插⼊切⾯的⼀个点,连接点可以理解为可能会触发AOP规则的所有点。(所有请求)

1.2.4 通知(Advice)

在AOP术语中,切面的工作被称之为通知。通知是切面在连接点上执行的动作。它定义了在何时(例如在方法调用之前或之后)以及如何(例如打印日志或进行性能监控)应用切面的行为。即,程序中被拦截请求触发的具体动作。

Spring 切⾯类中,可以在方法上使⽤以下注解,会设置⽅法为通知方法,在满⾜条件后会通知本
⽅法进⾏调⽤:

  1. 前置通知使⽤ @Before:通知⽅法会在⽬标⽅法调⽤之前执行。
  2. 后置通知使⽤ @After:通知⽅法会在⽬标⽅法返回或者抛出异常后调⽤。
  3. 返回之后通知使⽤ @AfterReturning:通知⽅法会在⽬标⽅法返回后调⽤。
  4. 抛异常后通知使⽤ @AfterThrowing:通知⽅法会在⽬标⽅法抛出异常后调⽤。
  5. 环绕通知使⽤ @Around:通知包裹了被通知的⽅法,在被通知的⽅法通知之前和调⽤之后执行⾃定义的行为。

1.3 AOP的使用场景

在做任何一个系统都需要登录功能,那么几乎想要使用这个系统都需要我们进行验证用户登录状态,我们之前的处理⽅式是每个 Controller 都要写⼀遍⽤户登录验证,然⽽当你的功能越来越多,那么你要写的登录验证也越来越多,⽽这些⽅法⼜是相同的,这么多的⽅法就会代码修改和维护的成本。对于这种功能统⼀,且使⽤的地⽅较多的功能,就可以考虑 AOP来统⼀处理了。

 

除了统一登录判断外,使用AOP还可以实现:

  • 用户登录验证
  • 统⼀⽇志记录
  • 统⼀⽅法执⾏时间统计
  • 统⼀的返回格式设置
  • 统⼀的异常处理
  • 事务的开启和提交等

二、Srping AOP 实现

Spring AOP 的实现步骤如下:

  1. 添加 Spring AOP 框架⽀持
  2. 定义切⾯和切点:(1)创建切面类(2)配置拦截规则
  3. 定义通知

2.1 添加Spring AOP 依赖

<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-bo
ot-starter-aop --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency>

2.2 定义切面和切点

使用 @Aspect 注解表明当前类为一个切面,而在切点中,我们要定义拦截的规则,具体实现如下:

@Component // 随着框架的启动而启动
@Aspect // 告诉框架我是一个切面类
public class UserAspect {// 定义切点(配置拦截规则)@Pointcut("execution(* com.example.demo.controller.UserController.*(..))")public void pointcut(){}
}

在上述实现代码中,pointcut 为一个空方法,只是起到一个“标识”的作用,标识下面的通知方法具体指的是哪个切点,切点可以有多个。

切点表达式由切点函数组成,其中 execution() 是最常⽤的切点函数,⽤来匹配⽅法,语法为:

execution(<修饰符><返回类型><包.类.⽅法(参数)><异常>)
修饰符和异常可以省略

常见的切点表达式的示例:

  • 匹配特定类的所有方法:
  • execution(* com.example.MyClass.*(..)):匹配 com.example.MyClass 类中的所有方法。
  • 匹配特定包下的所有方法:
  • execution(* com.example.*.*(..)):匹配 com.example 包及其子包下的所有方法。
  • 匹配特定方法名的方法:
  • execution(* com.example.MyClass.myMethod(..)):匹配 com.example.MyClass 类中名为 myMethod 的方法。
  • 匹配特定方法参数类型的方法:
  • execution(* com.example.MyClass.myMethod(String, int)):匹配 com.example.MyClass 类中具有一个 String 参数和一个 int 参数的 myMethod 方法。
  • 匹配特定返回类型的方法:
  • execution(String com.example.MyClass.myMethod(..)):匹配 com.example.MyClass 类中返回类型为 String 的 myMethod 方法。
package com.example.demo.controller;import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
@RequestMapping("/user")
public class UserController {@RequestMapping("/hi")public String sayHi(String name){System.out.println("执行了Hi");return "Hi," + name;}@RequestMapping("/hello")public String sayHello(){System.out.println("执行了Hello");return "Hello,world";}
}

2.3 定义通知

通知定义的是被拦截方法具体要执行的业务。我们上面列出了可以使用哪些通知~这里举出例子

package com.example.demo.aop;import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;@Component // 随着框架的启动而启动
@Aspect // 告诉框架我是一个切面类
public class UserAspect {// 定义切点(配置拦截规则)@Pointcut("execution(* com.example.demo.controller.UserController.*(..))")public void pointcut(){}@Before("pointcut()")public void beforeAdvice(){System.out.println("执行了前置通知~");}@After("pointcut()")public void AfterAdvice(){System.out.println("执行了后置通知~");}/*** 环绕通知* @param joinPoint* @return*/@Around("pointcut()")public Object aroundAdvice(ProceedingJoinPoint joinPoint){System.out.println("进入了环绕通知~");Object obj = null;try {// 执⾏拦截⽅法obj = joinPoint.proceed();} catch (Throwable e) {e.printStackTrace();}System.out.println("退出了环绕通知~");return obj;}}

环绕通知是在前置通知之前和后置通知之后运行的~

  

三、Spring AOP 实现原理

Spring AOP 是通过动态代理的⽅式,在运⾏期将 AOP 代码织⼊到程序中的,它的实现⽅式有两种:JDK Proxy 和 CGLIB因此,Spring 对 AOP 的支持局限于方法级别的拦截。

  1. 默认情况下,实现了接⼝的类,使⽤ AOP 会基于 JDK ⽣成代理类
  2. 没有实现接⼝的类,会基于 CGLIB ⽣成代理类
     

3.1 什么是动态代理?

动态代理(Dynamic Proxy)是一种设计模式,它允许 在运行时创建代理对象,并将方法调用转发给实际的对象。 动态代理可以用于实现横切关注点(如日志记录、性能监控、事务管理等)的功能,而无需修改原始对象的代码。

在Java中,动态代理通常使用 java.lang.reflect.Proxy 类和 java.lang.reflect.InvocationHandler 接口来实现。

调用者在调用方法时,会先转发给代理类创建的代理对象,随后再由代理对象转发给目标对象。

以下是使用动态代理的一般步骤:

  1. 创建一个实现InvocationHandler接口的类,该类将作为代理对象的调用处理程序。在InvocationHandler接口的invoke方法中,可以定义在方法调用前后执行的逻辑。
  2. 使用Proxy类的newProxyInstance方法创建代理对象。该方法接受三个参数:类加载器、代理接口数组和调用处理程序。它将返回一个实现指定接口的代理对象。
  3. 使用代理对象调用方法。当调用代理对象的方法时,实际上会调用调用处理程序的invoke方法,并将方法调用转发给实际的对象。
     

3.2 JDK 动态代理实现

先通过实现 InvocationHandler 接⼝创建⽅法调⽤处理器,再通过 Proxy 来创建代理类。

import org.example.demo.service.AliPayService;
import org.example.demo.service.PayService;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
//动态代理:使⽤JDK提供的api(InvocationHandler、Proxy实现),此种⽅式实现,要求被代理类必须实现接⼝
public class PayServiceJDKInvocationHandler implements InvocationHandler {//⽬标对象即就是被代理对象private Object target;public PayServiceJDKInvocationHandler( Object target) {this.target = target;}//proxy代理对象@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {//1.安全检查System.out.println("安全检查");//2.记录⽇志System.out.println("记录⽇志");//3.时间统计开始System.out.println("记录开始时间");//通过反射调⽤被代理类的⽅法Object retVal = method.invoke(target, args);//4.时间统计结束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();}
}

3.3 CGLIB 动态代理实现

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import org.example.demo.service.AliPayService;
import org.example.demo.service.PayService;
import java.lang.reflect.Method;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 {
//1.安全检查System.out.println("安全检查");
//2.记录⽇志System.out.println("记录⽇志");
//3.时间统计开始System.out.println("记录开始时间");
//通过cglib的代理⽅法调⽤Object retVal = methodProxy.invoke(target, args);
//4.时间统计结束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();}
}

3.4 JDK 和 CGLIB 实现的区别

  1. JDK 实现,要求被代理类必须实现接口, 之后是通过 InvocationHandler 及 Proxy,在运⾏时动态的在内存中⽣成了代理类对象,该代理对象是通过实现同样的接⼝实现(类似静态代理接⼝实现的⽅式),只是该代理类是在运⾏期时,动态的织⼊统⼀的业务逻辑字节码来完成。
  2. CGLIB 实现,被代理类可以不实现接口, 是通过继承被代理类,在运⾏时动态的⽣成代理类对象。

相关文章:

【Spring】Spring AOP 初识及实现原理解析

博主简介&#xff1a;想进大厂的打工人博主主页&#xff1a;xyk:所属专栏: JavaEE进阶 目录 文章目录 一、初识AOP 1.1 什么是AOP&#xff1f; 1.2 AOP的组成 1.2.1 切面&#xff08;Aspect&#xff09; 1.2.2 切点&#xff08;Pointcut&#xff09; 1.2.3 连接点&…...

【Express.js】集成Redis

集成Redis 本节我们介绍在 express.js 中集成 redis. Redis是一个高性能的key-value内存数据库&#xff0c;支持事务、队列、持久化等特性&#xff0c;常用于高并发性能场景。 准备工作 创建一个 express.js 项目&#xff08;本文基于evp-express-cli&#xff09;在开发环境…...

StringBuilder创建的对象如何清空

要清空一个StringBuilder对象&#xff0c;可以使用其实例方法setLength(int newLength)来实现。将newLength参数设置为0&#xff0c;就可以将StringBuilder的内容清空。以下是一个示例代码&#xff1a; StringBuilder sb new StringBuilder(); // 添加一些内容到StringBuilde…...

mybatis-plus实现mysql自定义IKeyGenerator

1. IKeyGenerator主键生成 内置实现类&#xff0c;缺少mysql&#xff0c;因为mysql不支持序列&#xff0c;只能通过表模拟序列 H2KeyGenerator PostgreKeyGenerator KingbaseKeyGenerator 人大金仓的KES数据库 DB2KeyGenerator OracleKeyGenerator2. 新建表模拟序列 CREATE T…...

山西电力市场日前价格预测【2023-08-11】

日前价格预测 预测明日&#xff08;2023-08-11&#xff09;山西电力市场全天平均日前电价为367.15元/MWh。其中&#xff0c;最高日前电价为408.91元/MWh&#xff0c;预计出现在20: 00。最低日前电价为343.90元/MWh&#xff0c;预计出现在02: 30。 价差方向预测 1&#xff1a; 实…...

浏览器无法连接网络问题

问题描述 电脑其他程序都能正常联网&#xff0c;但是所有的浏览器都无法联网&#xff0c;同时外部网站都能ping通 问题诊断 查看电脑Internet连接的问题报告显示&#xff1a;该设备或资源(Web 代理)未设置为接受端口"7890"上的连接。 解决方案 经过检查发现不是IP地址…...

ZyjDataLink 全量MySQL同步程序 - 开发过程 01

开发过程由本人从 架构设计 到 代码实现 独立完成&#xff0c;通过该博客记录分享开发经验 ZyjDataLink 当前的目标是做到 MySQL大数据量的快速同步&#xff0c;后期希望扩展的功能 高度可操作性&#xff0c;融入增量数据库同步&#xff0c;跨数据库同步 ZyjDataLink 需求分析…...

为什么说Python股票接口是连接投资与编程的桥梁?

在现代金融领域&#xff0c;投资与技术的结合日益紧密&#xff0c;而Python作为一种强大的编程语言&#xff0c;为投资者提供了开发股票交易策略和分析市场数据的高效工具。 一、Python股票接口的重要性&#xff1a; Python作为一种易学易用的编程语言&#xff0c;逐渐成为金融…...

kubectl,helm安装到window

1、Windows创建目录和文件 创建文件到指定目录记得切换目录 安装到家目录 wangdachuD-M70K45QX-1544 MINGW64 ~ $ mkdir -p .kubewangdachuD-M70K45QX-1544 MINGW64 ~ $ vi configwangdachuD-M70K45QX-1544 MINGW64 ~ $ pwd /c/Users/wangdachu 2、Windows安装kubectl到当前…...

【BASH】回顾与知识点梳理(目录)

【BASH】回顾与知识点梳理&#xff08;目录&#xff09; 一. 认识与学习 BASH二. Shell 的变量功能三. 命令别名与历史命令四. Bash Shell 的操作环境五. 数据流重导向六. 管线命令 (pipe)七. 前六章知识点总结及练习八. 正则表达式&#xff08;正规表示法&#xff09;九. 扩展…...

TFRecords详解

内容目录 TFRecords 是什么序列化(Serialization)tf.data 图像序列化&#xff08;Serializing Images)tf.Example函数封装 小结 TFRecords 是什么 TPU拥有八个核心&#xff0c;充当八个独立的工作单元。我们可以通过将数据集分成多个文件或分片&#xff08;shards&#xff09;…...

【多维定向滤波器组和表面波】表面变换:用于高效表示多维 s 的多分辨率变换(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…...

45.113.201.X服务器远程不上是什么原因,有什么办法解决?

45.113.201.1远程登录不上可能有多种原因导致&#xff0c;以下是一些常见的问题和解决方法&#xff1a; 网络连接问题&#xff1a;确保本地网络连接正常&#xff0c;尝试通过其他设备或网络连接服务器&#xff0c;确认是否是网络问题导致无法远程登录。 IP地址或端口错误&…...

微信小程序 地图map(电子围栏圆形和多边形)

正常情况下是没有手机上画电子围栏的&#xff0c;公共平台上我也没找到&#xff0c;所以走了一个歪点子&#xff0c;就是给地图添加点击事件&#xff0c;记录点的位置&#xff0c;在画到电子围栏上就是添加电子围栏了&#xff0c;如果只是显示电子围栏就简单了 一、多边形电子…...

Dockerfile 文件

dockerfile是一个文本文件&#xff0c;包含一条条指令&#xff0c;每条指令都会构建一层镜,一般分为四部分&#xff1a;基础镜像信息、维护者信息、镜像操作指令和容器启动时执行指令&#xff0c;#为 Dockerfile 中的注释。 docker build 基于dockerfile制作镜像 OPTIONS参数 …...

ssm学院党员管理系统源码和论文PPT

ssm学院党员管理系统源码和论文PPT002 开发工具&#xff1a;idea 数据库mysql5.7(mysql5.7最佳) 数据库链接工具&#xff1a;navcat,小海豚等 开发技术&#xff1a;java ssm tomcat8.5 选题意义、价值和目标&#xff1a; 随着鄂尔多斯应用技术学院招生规模的不断扩大&…...

文件数字水印,附一种纯文本隐写术数字水印方法

数字水印&#xff08;Digital Watermark&#xff09;是一种在数字媒体文件中嵌入隐藏信息的技术。这些数字媒体可以是图片、音频、视频或文本等。数字水印不会对原始文件造成明显的视觉或听觉变化&#xff0c;但可以在一定程度上保护知识产权&#xff0c;追踪数据来源&#xff…...

测试开发(一) 使用Vue开发chrome插件

目录 一、引言 二、功能说明 三、【配置】操作演示 四、【请求拦截】演示 不断访问博客&#x...

游戏行业实战案例 4 :在线时长分析

【面试题】某游戏数据后台设有「登录日志」和「登出日志」两张表。 「登录日志」记录各玩家的登录时间和登录时的角色等级。 「登出日志」记录各玩家的登出时间和登出时的角色等级。 其中&#xff0c;「角色id」字段唯一识别玩家。 游戏开服前两天&#xff08; 2022-08-13 至 …...

记一次图片压缩引发的生产问题

省流&#xff1a; 死循环导致没有commit&#xff08;提交事务&#xff09;&#xff0c;transaction一直没有结束。 正文&#xff1a; 调用接口报错&#xff1a; jdbc报错&#xff1a; MySQLTransactionRollbackException: Lock wait timeout exceeded; try restarting tran…...

Redis相关知识总结(缓存雪崩,缓存穿透,缓存击穿,Redis实现分布式锁,如何保持数据库和缓存一致)

文章目录 1.什么是Redis&#xff1f;2.为什么要使用redis作为mysql的缓存&#xff1f;3.什么是缓存雪崩、缓存穿透、缓存击穿&#xff1f;3.1缓存雪崩3.1.1 大量缓存同时过期3.1.2 Redis宕机 3.2 缓存击穿3.3 缓存穿透3.4 总结 4. 数据库和缓存如何保持一致性5. Redis实现分布式…...

【论文笔记】若干矿井粉尘检测算法概述

总的来说&#xff0c;传统机器学习、传统机器学习与深度学习的结合、LSTM等算法所需要的数据集来源于矿井传感器测量的粉尘浓度&#xff0c;通过建立回归模型来预测未来矿井的粉尘浓度。传统机器学习算法性能易受数据中极端值的影响。YOLO等计算机视觉算法所需要的数据集来源于…...

EtherNet/IP转DeviceNet协议网关详解

一&#xff0c;设备主要功能 疆鸿智能JH-DVN-EIP本产品是自主研发的一款EtherNet/IP从站功能的通讯网关。该产品主要功能是连接DeviceNet总线和EtherNet/IP网络&#xff0c;本网关连接到EtherNet/IP总线中做为从站使用&#xff0c;连接到DeviceNet总线中做为从站使用。 在自动…...

零基础设计模式——行为型模式 - 责任链模式

第四部分&#xff1a;行为型模式 - 责任链模式 (Chain of Responsibility Pattern) 欢迎来到行为型模式的学习&#xff01;行为型模式关注对象之间的职责分配、算法封装和对象间的交互。我们将学习的第一个行为型模式是责任链模式。 核心思想&#xff1a;使多个对象都有机会处…...

深度学习习题2

1.如果增加神经网络的宽度&#xff0c;精确度会增加到一个特定阈值后&#xff0c;便开始降低。造成这一现象的可能原因是什么&#xff1f; A、即使增加卷积核的数量&#xff0c;只有少部分的核会被用作预测 B、当卷积核数量增加时&#xff0c;神经网络的预测能力会降低 C、当卷…...

云原生安全实战:API网关Kong的鉴权与限流详解

&#x1f525;「炎码工坊」技术弹药已装填&#xff01; 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 一、基础概念 1. API网关&#xff08;API Gateway&#xff09; API网关是微服务架构中的核心组件&#xff0c;负责统一管理所有API的流量入口。它像一座…...

Golang——6、指针和结构体

指针和结构体 1、指针1.1、指针地址和指针类型1.2、指针取值1.3、new和make 2、结构体2.1、type关键字的使用2.2、结构体的定义和初始化2.3、结构体方法和接收者2.4、给任意类型添加方法2.5、结构体的匿名字段2.6、嵌套结构体2.7、嵌套匿名结构体2.8、结构体的继承 3、结构体与…...

Kafka主题运维全指南:从基础配置到故障处理

#作者&#xff1a;张桐瑞 文章目录 主题日常管理1. 修改主题分区。2. 修改主题级别参数。3. 变更副本数。4. 修改主题限速。5.主题分区迁移。6. 常见主题错误处理常见错误1&#xff1a;主题删除失败。常见错误2&#xff1a;__consumer_offsets占用太多的磁盘。 主题日常管理 …...

Kubernetes 网络模型深度解析:Pod IP 与 Service 的负载均衡机制,Service到底是什么?

Pod IP 的本质与特性 Pod IP 的定位 纯端点地址&#xff1a;Pod IP 是分配给 Pod 网络命名空间的真实 IP 地址&#xff08;如 10.244.1.2&#xff09;无特殊名称&#xff1a;在 Kubernetes 中&#xff0c;它通常被称为 “Pod IP” 或 “容器 IP”生命周期&#xff1a;与 Pod …...

2.3 物理层设备

在这个视频中&#xff0c;我们要学习工作在物理层的两种网络设备&#xff0c;分别是中继器和集线器。首先来看中继器。在计算机网络中两个节点之间&#xff0c;需要通过物理传输媒体或者说物理传输介质进行连接。像同轴电缆、双绞线就是典型的传输介质&#xff0c;假设A节点要给…...