Spring AOP(AOP概念、组成、Spring AOP实现及实现原理)

news2024/10/7 20:36:45

文章目录

  • 1. Spring AOP 是什么
  • 2. 为什么要用 AOP
  • 3. 怎么学 Spring AOP
  • 4. AOP 组成
  • 5. Spring AOP 实现
    • 5.1 添加 Spring AOP 框架支持
    • 5.2 定义切面和切点
    • 5.3 实现通知方法
    • 5.4 使⽤ AOP 统计 UserController 每个⽅法的执⾏时间 StopWatch
    • 5.4 切点表达式说明 AspectJ
  • 6. Spring AOP 实现原理
    • 6.1 生成代理的时机 :织入(Weaving)
    • 6.2 JDK 动态代理实现
    • 6.3 CGLIB 动态代理实现
    • 6.4 JDK 和 CGLIB 实现的区别

1. Spring AOP 是什么

学习 Spring AOP 之前,先要了解 AOP 是什么

AOP(Aspect Oriented Programming):面向切面编程,它和 OOP(面向对象编程)类似。

它是一种思想,是对某一类事情的集中处理。

比如用户登录权限的效验,在学习 AOP 之前,在需要判断用户登录的页面,都要各自实现或调用用户验证的方法,学习 AOP 之后,我们只需要在某一处配置一下,那么所有需要判断用户登录的页面就全部可以实现用户登录验证了,不用在每个方法中都写用户登录验证了

AOP 是一种思想,而 Spring AOP 是实现(框架),这种关系和 IOC(思想)与 DI(实现)类似


2. 为什么要用 AOP

  1. 高频:对于这种功能统一,且使用地方较多的功能,可以考虑用 AOP 来处理(比如 用户登录验证)
  2. 使⽤ AOP 可以扩充多个对象的某个能⼒,AOP 可以说是 OOP 的补充和完善(比如 现在要实现的业务和这个通用的功能没什么关系,但处于安全考虑,又必须进行登录的验证)

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

  • 统一日志处理
  • 统一方法执行时间统计
  • 统一的返回格式设置
  • 统一的异常处理
  • 事务的开启和提交等

3. 怎么学 Spring AOP

Spring AOP 学习主要分为3个部分

  1. 学习 AOP 是如何组成的
  2. 学习 Spring AOP 使用
  3. 学习 Spring AOP 实现原理

4. AOP 组成

(1)切面(Aspect)

定义 AOP 是针对某个统一的功能的,这个功能就叫做一个切面,比如用户登录功能或方法的统计日志,他们就各是一个切面。切面是由切点和通知组成的

(2)连接点(Join Point)

所有可能触发 AOP(拦截方法的点)就称为连接点

(3)切点(Pointcut)

切点的作用就是提供一组规则来匹配连接点,给满足规则的连接点添加通知,总的来说就是,定义 AOP 拦截的规则的

切点相当于保存了众多连接点的一个集合(如果把切点看成一个表,而连接点就是表中一条一条的数据)

(4)通知(Advice)

切面的工作就是通知

通知:规定了 AOP 执行的时机和执行的方法

Spring 切面类中,可以在方法上使用以下注解,会设置方法为通知方法,在满足条件后悔通知本方法进行调用

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

举个例子,在一个生产型公司中

通知相当于底层的执行者,切点是小领导制定规则,切面是大领导制定公司的发展方向,连接点是属于一个普通的消费者用户

以多个⻚⾯都要访问⽤户登录权限为例子,AOP 整个组成部分如图所示

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zkAm0DT8-1676207159825)(C:\Users\28463\AppData\Roaming\Typora\typora-user-images\1676183469495.png)]


5. Spring AOP 实现

Spring AOP 实现步骤

  1. 添加 Spring AOP 框架支持
  2. 定义切面和切点
  3. 实现通知

接下来我们使⽤ Spring AOP 来实现⼀下 AOP 的功能,完成的⽬标是拦截所有 UserController ⾥⾯的

⽅法,每次调⽤ UserController 中任意⼀个⽅法时,都执⾏相应的通知事件。

5.1 添加 Spring AOP 框架支持

在中央仓库中搜锁 Spring AOP Maven Repository: Search/Browse/Explore (mvnrepository.com)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uIsDmKWi-1676207159826)(C:\Users\28463\AppData\Roaming\Typora\typora-user-images\1676185167353.png)]

在 pom.xml 中添加如下配置:

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

5.2 定义切面和切点

@Aspect // 当前类是一个切面
@Component
public class UserAspect {
    // 定义一个切点(设置拦截规则)
    @Pointcut("execution(* com.example.springaop.controller.UserController.*(..))")
    public void pointcut() {
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-p4mQspnI-1676207159827)(C:\Users\28463\AppData\Roaming\Typora\typora-user-images\1676186116916.png)]

5.3 实现通知方法

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

实现通知方法也就是在什么时机执行什么方法

@Aspect // 当前类是一个切面
@Component
public class UserAspect {
    // 定义一个切点(设置拦截规则)
    @Pointcut("execution(* com.example.springaop.controller.UserController.*(..))")
    public void pointcut() {
    }

    // 定义 pointcut 切点的前置通知
    @Before("pointcut()")
    public void doBefore() {
        System.out.println("执行前置通知");
    }

    // 后置通知
    @After("pointcut()")
    public void doAfter() {
        System.out.println("执行后置通知");
    }

    // 返回之后通知
    @AfterReturning("pointcut()")
    public void doAfterReturning() {
        System.out.println("执行返回之后通知");
    }

    // 抛出异常之后通知
    @AfterThrowing("pointcut()")
    public void doAfterThrowing() {
        System.out.println("执行抛出异常之后通知");
    }
}
@RestController
@RequestMapping("/user")
public class UserController {

    @RequestMapping("/sayhi")
    public String sayHi() {
        System.out.println("sayhi 方法被执行");
        int num = 10/0;
        return "你好,java";
    }

    @RequestMapping("/sayhi2")
    public String sayHi2() {
        System.out.println("sayhi2 方法被执行");
        return "你好,java2";
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qVSOeHQj-1676207159828)(C:\Users\28463\AppData\Roaming\Typora\typora-user-images\1676189891534.png)]

环绕通知:@Around:通知包裹了被通知的方法,在被通知的方法通知之前和调用之后执行自定义的行为

// 添加环绕通知
@Around("pointcut()")
public Object doAround(ProceedingJoinPoint joinPoint) {
    Object result = null;
    System.out.println("环绕通知:前置方法");
    try {
        // 执行拦截方法
        result = joinPoint.proceed();
    } catch (Throwable e) {
        e.printStackTrace();
    }
    System.out.println("环绕通知:后置方法");
    return result;
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5xizlv7M-1676207159828)(C:\Users\28463\AppData\Roaming\Typora\typora-user-images\1676190278115.png)]

5.4 使⽤ AOP 统计 UserController 每个⽅法的执⾏时间 StopWatch

Spring AOP 中统计时间用 StopWatch 对象

    // 添加环绕通知
    @Around("pointcut()")
    public Object doAround(ProceedingJoinPoint joinPoint) {
        // spring 中的时间统计对象
        StopWatch stopWatch = new StopWatch();
        Object result = null;
        try {
            stopWatch.start(); // 统计方法的执行时间,开始计时
            // 执行目标方法,以及目标方法所对应的相应通知
            result = joinPoint.proceed();
            stopWatch.stop(); // 统计方法的执行时间,停止计时
        } catch (Throwable e) {
            e.printStackTrace();
        }
        System.out.println(joinPoint.getSignature().getDeclaringTypeName() + "." +
                joinPoint.getSignature().getName() +
                "执行花费的时间:" + stopWatch.getTotalTimeMillis() + "ms");
        return result;
    }

5.4 切点表达式说明 AspectJ

AspectJ 表达式语法:SpringAOP & AspectJ

    @Pointcut("execution(* com.example.springaop.controller.UserController.*(..))")

AspectJ 语法(Spring AOP 切点的匹配语法):

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

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

AspectJ ⽀持三种通配符

* :匹配任意字符,只匹配⼀个元素(包,类,或⽅法,⽅法参数)

… :匹配任意字符,可以匹配多个元素 ,在表示类时,必须和 * 联合使⽤。

+ :表示按照类型匹配指定类的所有类,必须跟在类名后⾯,如 com.cad.Car+ ,表示继承该类的所有⼦类包括本身

修饰符,一般省略

  • public 公共方法
  • *任意

返回值,不能省略

  • void 返回没有值
  • String 返回值字符串
  • *任意

包,通常不省略,但可以省略

  • com.gyf.crm 固定包
  • com.gyf.crm.*.service crm 包下面子包任意(例如:com.gyf.crm.staff.service)
  • com.gyf.crm… crm 包下面的所有子包(含自己)
  • com.gyf.crm.*service… crm 包下面任意子包,固定目录 service,service 目录任意包

类,通常不省略,但可以省略

UserServiceImpl 指定类

*Impl 以 Impl 结尾

User* 以 User 开头

*任意

方法名,不能省略

addUser 固定方法

add* 以 add 开头

*DO 以 DO 结尾

*任意

参数

() 无参

(int) 一个整形

(int,int)两个整型

(…) 参数任意

throws可省略,一般不写

表达式示例

  • execution(* com.cad.demo.User.*(…)) :匹配 User 类⾥的所有⽅法
  • execution(* com.cad.demo.User+.*(…)) :匹配该类的⼦类包括该类的所有⽅法
  • execution(* com.cad..(…)) :匹配 com.cad 包下的所有类的所有⽅法
  • execution(* com.cad….(…)) :匹配 com.cad 包下、⼦孙包下所有类的所有⽅法
  • execution(* addUser(String, int)) :匹配 addUser ⽅法,且第⼀个参数类型是 String,第⼆个参数类型是 int

6. Spring AOP 实现原理

Spring AOP 是构建在动态代理基础上,因此 Spring 对 AOP 的支持局限于方法级别的拦截

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pDirrfMk-1676207159829)(C:\Users\28463\AppData\Roaming\Typora\typora-user-images\1676192122209.png)]

Spring AOP 动态代理实现:

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

  1. JDK Proxy(JDK 动态代理)

  2. CGLIB Proxy:默认情况下 Spring AOP 都会采用 CGLIB 来实现动态代理,因为效率高

    CGLIB 实现原理:通过继承代理对象来实现动态代理的(子类拥有父类的所有功能)

    CGLIB 缺点:不能代理最终类(也就是被 final 修饰的类)

6.1 生成代理的时机 :织入(Weaving)

织入是把切面应用到目标对象并创建新的代理对象的过程,切面在指定的连接点被织入到目标对象中

在目标对象的生命周期中有多个点可以进行织入

  1. 编译期:切面在目标类编译时被织入,这种方法需要特殊的编译器,AspectJ 的织入编译器就是以这种方式织入切面的
  2. 类加载期:切面在目标类加载到 JVM 时被织入,这种方式需要特殊的类加载器,它可以在目标类被引入应用之前增强该目标类的字节码,AspectJ5 的加载时织入 (load-time weaving. LTW)就支持以这种方式织入切面
  3. 运行期:切面在应用运行的某一时刻被织入,一般情况下,在织入切面时,AOP容器会为目标对象动态创建一个代理对象,Spring AOP 就是以这种方式织入切面的

6.2 JDK 动态代理实现

JDK 动态代理就是依靠反射来实现的

//动态代理:使⽤JDK提供的api(InvocationHandler、Proxy实现),此种⽅式实现,要求被
代理类必须实现接⼝
public class PayServiceJDKInvocationHandler implements InvocationHandler {
 
 	//⽬标对象即就是被代理对象
 	private Object target;
 
 	public PayServiceJDKInvocationHandler( Object target) {
		 this.target = target;
 	}
 
	//proxy代理对象
 	@Override
	public 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();
 	}
}

6.3 CGLIB 动态代理实现

public class PayServiceCGLIBInterceptor implements MethodInterceptor {
 	//被代理对象
	 private Object target;
 
	 public PayServiceCGLIBInterceptor(Object target){
	 	this.target = target;
 	 }
 
	 @Override
	 public Object intercept(Object o, Method method, Object[] args, MethodProxymethodProxy)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();
 	 }
}

6.4 JDK 和 CGLIB 实现的区别

  1. JDK 实现,要求被代理类必须实现接口,之后是通过 InvocationHander 及 Proxy,在运行时动态的在内存中生成了代理对象,该代理对象是通过实现同样的接口实现(类似静态代理接口实现的方式),只是该代理类是在运行期时,动态的织入统一的业务逻辑字节码来完成的
  2. CGLIB 实现,被代理类可以不实现接口,是通过继承被代理类,在运行时动态的生成代理类对象,这种方式实现方式效率高

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

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

相关文章

【SPSS】基础图形的绘制(条形图、折线图、饼图、箱图)详细操作过程

&#x1f935;‍♂️ 个人主页&#xff1a;艾派森的个人主页 ✍&#x1f3fb;作者简介&#xff1a;Python学习者 &#x1f40b; 希望大家多多支持&#xff0c;我们一起进步&#xff01;&#x1f604; 如果文章对你有帮助的话&#xff0c; 欢迎评论 &#x1f4ac;点赞&#x1f4…

Qt-FFmpeg开发-实现录屏功能(10)

#音视频/FFmpeg #Qt Qt-FFmpeg开发-实现录屏功能&#x1f4ac; 文章目录Qt-FFmpeg开发-实现录屏功能&#x1f4ac;1、概述&#x1f4a5;2、实现效果&#x1f4a8;3、FFmpeg录屏代码流程&#x1f441;️‍&#x1f5e8;️4、主要代码&#x1f919;5、完整源代码&#x1f90f;更…

Doris单机部署

文章目录1. 前言2. 安装3. 启动4. 使用1. 前言 Apache Doris 是一款现代 MPP (Massively Parallel Processing大规模并行处理) 的分布式 SQL 分析数据库&#xff0c;所谓分析数据库就是将其数据集分布在许多机器或节点上&#xff0c;以处理大量数据&#xff0c;采用 Apache 2.0…

几十亿工单表,查询优化案例

前言: 之前在某大型保险公司担任技术经理&#xff0c;负责优化话务系统模块&#xff0c;由于系统已经运行10年之久&#xff0c;尤其在话务系统中&#xff0c;沉积了几十亿的话务信息表&#xff0c;业务人员反馈&#xff0c;话务系统历史数据查询部分已经完全查询不动&#xff0…

Spring Cloud Nacos源码讲解(二)- Nacos客户端服务注册源码分析

Nacos客户端服务注册源码分析 服务注册信息 ​ 我们从Nacos-Client开始说起&#xff0c;那么说到客户端就涉及到服务注册&#xff0c;我们先了解一下Nacos客户端都会将什么信息传递给服务器&#xff0c;我们直接从Nacos Client项目的NamingTest说起 public class NamingTest…

less、sass、webpack(前端工程化)

目录 一、Less 1.配置less环境 1.先要安装node&#xff1a;在cmd中&#xff1a;node -v检查是否安装node 2.安装less :cnpm install -g less 3.检查less是否安装成功&#xff1a;lessc -v 4.安装成功后&#xff0c;在工作区创建xx.less文件 5.在控制台编译less,命令&…

Spring Cloud——流监控Dashboard

一、编写三个module 1. springcloud-consumer-hystrix-dashboard 1.导入依赖 <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-hystrix</artifactId><version>1.4.6.RELEASE</version>…

【服务器数据恢复】raid5磁盘阵列硬盘离线的数据恢复案例

服务器数据恢复环境&#xff1a; 某公司一台服务器组建了一组raid5磁盘阵列&#xff0c;作为共享存储池使用。该服务器存储数据库文件和普通文件。 服务器故障&检测&#xff1a; RAID5磁盘阵列的硬盘掉线导致服务器操作系统识别不到D分区。管理员重启了服务器&#xff0c;服…

如何发布新闻稿,包含编写新闻稿、发布渠道

发布新闻稿是一种重要的传播方式&#xff0c;它可以让公众了解你的新闻&#xff0c;并提高新闻的知名度和可信度。在这篇文章中&#xff0c;我将详细介绍如何发布新闻稿&#xff0c;包括选择发布渠道、编写新闻稿、发布新闻稿和推广新闻稿等方面的内容。1、选择发布渠道在发布新…

VirtualBox压缩VDI文件 VDI文件瘦身方法(cenos7)

virtualbox虚拟机运行久了之后就会发现&#xff0c;磁盘镜像vdi文件越来越大。即使你把虚拟机中的大文件删除&#xff0c;这个vdi文件占用的空间还是不变。也就是说动态扩展的vdi文件只会大&#xff0c;不会小。那么大的文件对于备份和分享都不是很方便&#xff0c;所以有必要的…

Arduino-抢答器

抢答器实验实验器件&#xff1a;■ 按键开关&#xff1a;4 个■ 红色LED灯&#xff1a;1 个■ 黄色LED灯&#xff1a;1 个■ 绿色LED灯&#xff1a;1 个■ 220欧电阻&#xff1a;7 个■ 面包板&#xff1a;1 个■ 多彩杜邦线&#xff1a;若干实验连线将代码上传到开发板。程序代…

mysql 学习、复习资料整理详细

mysql 学习、复习资料整理详细1、数据库基础1.1 数据库设计遵循的原则1.2 数据库范式1.2 数据库完整性的实现2、mysql特点3、事务3.1 事务的四大特性 – ACID3.2 并发事务问题3.3 事务的四大隔离级别3.4 事务隔离级别操作sql3.5 事务原理 – LBCC MVCC3.4.1 行的隐藏列3.4.2 Re…

一文教你玩转 Apache Doris 分区分桶新功能|新版本揭秘

数据分片&#xff08;Sharding&#xff09;是分布式数据库分而治之 (Divide And Conquer) 这一设计思想的体现。过去的单机数据库在大数据量下往往面临存储和 IO 的限制&#xff0c;而分布式数据库则通过数据划分的规则&#xff0c;将数据打散分布至不同的机器或节点上&#xf…

【7/101】101次面试之测试技术面试题

01、什么是兼容性测试&#xff1f;兼容性测试侧重哪些方面&#xff1f;答&#xff1a;兼容性测试是一种软件测试类型&#xff0c;它的主要目的是确保一个应用程序在不同的操作系统、不同的浏览器、不同的设备、不同的网络环境等各种环境下能够正常运行&#xff0c;并且不会产生…

Vue使用ElementUI对表单元素进行自定义校验

前言 在使用ElementUI的表单元素时候&#xff0c;除了做一些简单的非空处理校验&#xff0c;在一些特殊的场合&#xff0c;还需要我们做一些自定义校验。 其实ElementUI不仅提供了基本的非空校验&#xff0c;也对我们提供了自定义检验。 在使用的时候还是遇到了一些坑&#…

华为OD机试 - 特异性双端队列(C++) | 附带编码思路 【2023】

刷算法题之前必看 参加华为od机试,一定要注意不要完全背诵代码,需要理解之后模仿写出,通过率才会高。 华为 OD 清单查看地址:https://blog.csdn.net/hihell/category_12199283.html 华为OD详细说明:https://dream.blog.csdn.net/article/details/128980730 华为OD机试题…

SAP中BOM基础数量及组件数量单位比例关系的注意事项

下图是BOM展开功能CS11在正式系统和测试系统的截图。从截图中的对比不难看出&#xff0c;最下级的原材料A20981-110在组件的数量为1&#xff0c;实际按BOM中的设定比例折算&#xff0c;应该是1个成品&#xff0c;对应需要0.125件原材料。但这里显示的并不是0.125PC&#xff0c;…

Ubuntu22.04 安装Mongodb6.X

Ubuntu22.04 安装Mongodb6.X 1、Mongodb简介 1.1 什么是MongoDB? Mongodb是一个跨平台的面向文档的NoSQL数据库。它使用带有可选模式的类似JSON的BSON来存储数据。应用程序可以以JSON格式检索信息。 1.2 MongoDB的优点 可以快速开发web型应用&#xff0c;因为灵活&#xff0c;…

sympy高斯光束模型

文章目录Gauss模型sympy封装实战sympy.phisics.optics.gaussopt集成了高斯光学中的常见对象&#xff0c;包括光线和光学元件等&#xff0c;有了这些东西&#xff0c;就可以制作一个光学仿真系统。Gauss模型 高斯光束的基本模型为 E(r,z)E0ω0ω(z)exp⁡[−r2ω2(z)]exp⁡[−ik…

自动增长配置不合理导致的性能抖动

背景客户收到了SQL专家云告警邮件&#xff0c;在凌晨2点到3点之间带有资源等待的会话数暴增&#xff0c;请我们协助分析。现象登录SQL专家云&#xff0c;进入活动会话的趋势分析页面&#xff0c;下钻到2点钟一个小时内的数据&#xff0c;看到每分钟的等待数都在100左右&#xf…