【Spring】Spring AOP

在这里插入图片描述

文章目录

  • 前言
  • 1. 什么是 AOP
  • 2. 什么是 Spring AOP
  • 3. Spring AOP 的使用
    • 引入 AOP 依赖
    • 编写 AOP 程序
  • 4. Spring AOP 详解
    • 4.1 Spring AOP 的概念
      • 4.1.1 切点
      • 4.1.2 连接点
      • 4.1.3 通知
      • 4.1.4 切面
    • 4.2 通知类型
    • 4.3 切点
    • 4.4 切面优先级 @Order注解
    • 4.5 切点表达式
      • 4.5.1 execution 切点表达式
      • 4.5.2 @annotation
        • 4.5.2.1 自定义注解
        • 4.5.2.2 切面类
        • 4.5.2.3 添加自定义注解

前言

前面我们学习了 SpringBoot 统一功能处理,这篇文章我将为大家分享 Spring 框架的第二大核心——AOP(第一大核心是 IOC)

1. 什么是 AOP

AOP(Aspect Oriented Programming)是一种编程范型,意为面向切面编程,什么是⾯向切面编程呢?切面就是指某⼀类特定问题,所以AOP也可以理解为面向特定⽅法编程,它通过预编译和运行期动态代理的方式实现程序功能的统一维护。AOP可以看作是OOP(面向对象编程)的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,同时也是函数式编程的一种衍生范型。

AOP的目标是实现对业务逻辑的各个部分进行隔离,从而降低业务逻辑各部分之间的耦合度,提高程序的可重用性,并提升开发效率。在AOP中,目标类代表核心代码和业务逻辑,而额外功能(也即AOP的功能)则包括日志处理、事务处理、异常处理、性能分析等。通过将目标类与额外功能结合,可以生成代理类。

AOP的原理基于Java的动态代理机制。通过预编译方式和运行期动态代理,AOP能够实现程序功能的统一维护,使得开发者能够更加专注于业务逻辑的实现,而无需过多关注其他非核心功能。

AOP 是面向某一特定问题的编程?那前面的统一功能处理什么呢?其实前面学习的 统一功能处理是 AOP 的具体实现和应用。AOP是⼀种思想,拦截器是AOP思想的⼀种实现。Spring框架实现了这种思想,提供了拦截器技术的相关接口。

2. 什么是 Spring AOP

知道了什么是 AOP,那么什么是 Spring AOP 呢?AOP 是一种思想,而 Spring AOP、AspectJ、GGLIB等叫做 AOP 的实现。

那么我们前面学习的拦截器、统一数据返回格式、统一异常处理这些 AOP 的实现不够吗?其实是不够的,拦截器作⽤的维度是URL(⼀次请求和响应),@ControllerAdvice 应⽤场景主要是全局异常处理(配合⾃定义异常效果更佳),数据绑定,数据预处理。AOP作⽤的维度更加细致(可以根据包、类、⽅法名、参数等进⾏拦截),能够实现更加复杂的业务逻辑。

假设一个项目中开发了很多业务功能,但是呢?由于一些业务的执行效率比较低,耗时较长,所以我们就需要对接口进行优化。首先需要做的就是找到耗时较长的业务方法,那么我们如何知道每个业务方法执行的时间呢?一个简单的方法就是可以通过获取到业务方法刚执行时候的时间 start,再记录这个业务方法刚结束时候的时间 end,用 end - statr,就得到了该业务方法的执行时间,那么,是否意味着我们需要在每个方法中都加上这段代码呢?

public void function1() {long startTime = System.currentTimeMillis();test();long endTime = System.currentTimeMillis();log.info("function1执行耗时" + (endTime - startTime));
}

如果每个方法都加上这样的逻辑的话,如果业务中的方法很多的话,那么这也是一个很大的工作量,所以这时候就可以用到我们的 AOP 了,AOP可以做到在不改动这些原始⽅法的基础上,针对特定的⽅法进行功能的增强。

3. Spring AOP 的使用

引入 AOP 依赖

AOP 属于第三方库,要想使用的话,就需要在 pom.xml 文件中引入对应的 AOP 依赖:

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId>
</dependency>

编写 AOP 程序

首先我们需要在类上加上 @Aspect 注解,表明这个类是一个 AOP 类:

在这里插入图片描述

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;@Slf4j
@Aspect
@Component
public class TimeAspect {@Around("execution(* com.example.springbootbook2.controller.*.*(..))")public Object recordTime(ProceedingJoinPoint pjp) throws Throwable {long begin = System.currentTimeMillis();//执行原始方法Object result = pjp.proceed();long end = System.currentTimeMillis();log.info(pjp.getSignature() + "执行耗时:{}ms",(end - begin));return result;}
}
  • @Aspect:标识这是⼀个切面类
  • @Around:环绕通知,在目标⽅法的前后都会被执⾏。后⾯的表达式表示对哪些方法进行增强
  • pjp.proceed():让原始⽅法执行

因为 @Around 注解的作用,这个代码被分为了三个部分:

在这里插入图片描述

看看执行效果:

在这里插入图片描述

可以看到:使用 AOP 可以在不更改原来业务代码的基础上,做出一些额外的工作。

AOP 面向切面编程的优势:

  • 代码无侵入:不修改原始的业务方法,就可以对原始的业务方法进行功能的增强或是功能的改变
  • 减少重复代码
  • 提高开发效率
  • 维护方便

既然 AOP 面向切面编程有这么多优势,拿那么接下来我们来详细的学习一下 Spring AOP。

4. Spring AOP 详解

4.1 Spring AOP 的概念

Spring AOP 需要了解的概念主要有:

  1. 切点
  2. 连接点
  3. 通知
  4. 切面

4.1.1 切点

切点(PointCur)也成为切入点,作用是提供一组规则,告诉程序哪些方法来进行功能增强。也就类似前面拦截器中的配置拦截路径。

也就是这个:

在这里插入图片描述
execution(* com.example.springbootbook2.controller.*.*(..))叫做切点表达式,这里后面为大家详细介绍。

4.1.2 连接点

满足切点表达式规则的方法,就是连接点,也就是可以被 AOP 控制的方法,参考上面的例子也就是 com.example.springboot2.controller 包下的所有类中的所有方法都叫做连接点。在我们上面的例子中主要体现在 pjp 参数中:

在这里插入图片描述
通过这个参数的 proceed() 方法可以执行目标方法。

4.1.3 通知

通知就是具体要做的工作,指那些重复的逻辑,也就是共性功能(最终体现为一个方法):

在这里插入图片描述

在AOP面向切面编程中,我们把这部分重复的代码逻辑抽取出来单独定义,这部分代码就是通知的内容。

4.1.4 切面

切面(Aspect)就是 切点(PointCut) + 通知(Advice)

通过切面就能够描述当前 AOP 程序需要针对于哪些⽅法,在什么时候执⾏什么样的操作。切面既包含了通知逻辑的定义,也包括了连接点的定义。

在这里插入图片描述
一个切面类可以存在多个切面。

4.2 通知类型

Spring AOP 中的通知类型有以下几种:

  1. @Around:环绕通知,此注解标注的通知方法在目标方法前,都被执行
  2. @Before:前置通知,此注解标注的通知方法在目标方法前被执行
  3. @After:后置通过,此注解标注的通知方法在目标方法后被执行,⽆论是否有异常都会执行
  4. AfterReturning:返回后通知,此注解标注的通知方法在目标方法后被执行,有异常不会被执行
  5. @AfterThrowing:异常后通知,此注解标注的通知方法发生异常后执行

接下来我们通过一个例子来了解这几种通知类型:

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;@Slf4j
@Aspect
@Component
public class AspectDemo {@Before("execution(* com.example.springaop.controller.*.*(..))")public void doBefore() {log.info("执行 Before 方法");}@After("execution(* com.example.springaop.controller.*.*(..))")public void doAfter() {log.info("执行 After 方法");}@AfterReturning("execution(* com.example.springaop.controller.*.*(..))")public void doAfterReturning() {log.info("执行 AfterReturning 方法");}@AfterThrowing("execution(* com.example.springaop.controller.*.*(..))")public void doAfterThrowing() {log.info("执行 AfterThrowing 方法");}@Around("execution(* com.example.springaop.controller.*.*(..))")public Object doAround(ProceedingJoinPoint pjp) throws Throwable {log.info("Around 方法开始执行");Object result = pjp.proceed();log.info("Around 方法执行后");return result;}
}
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@Slf4j
@RestController
@RequestMapping("test")
public class TestController {@RequestMapping("/t1")public String t1() {log.info("目标方法执行");return "hello";}
}

在这里插入图片描述
这里少了一个日志就是 AfterThrowing 方法打印的日志,这是因为我们这个方法没有出现错误,所以我们重新定义一个内部会剖出异常的方法:

@RequestMapping("/t2")
public Integer t2() {log.info("目标方法2执行");Integer result = 10/0;return result;
}

在这里插入图片描述
可以看到这几个通知类型的执行顺序:

目标方法中不出现异常:Around通知类型的目标方法执行前的逻辑——>Before通知类型——>目标方法——>AfterReturning通知类型——>After通知类型——>Around通知类型的目标方法执行之后的逻辑

目标方法中出现异常:Around通知类型的目标方法执行前的逻辑——>Before通知类型——>目标方法——>AfterThrowing通知类型——>After通知类型

注意:

  • @Around 环绕通知需要调用 ProceedingJoinPoint.proceed() 来让原始方法执行,其他通过不需要考虑目标方法的执行
  • @Around 环绕通知方法的返回值,必须指定为 Object,来接收原始方法的返回值,否则原始方法执行完毕,是获取不到返回值的
  • ⼀个切面类可以有多个切点

4.3 切点

通过前面的例子,我们可以发现,同样的切点表达式写了很多,那么是否有一种方法可以节省相同切点表达式的书写呢?当然是可以的,我们程序员可是以“懒”著称的。

我们可以使用 @PointCut 注解将相同的切点表达式提取出来,需要用到时引用该切点表达式即可。

@Pointcut("切点表达式")
修饰限定词 返回值 方法名(){}
public class AspectDemo {@Pointcut("execution(* com.example.springaop.controller.*.*(..))")private void pt(){}@Before("pt()")public void doBefore() {log.info("执行 Before 方法");}@After("pt()")public void doAfter() {log.info("执行 After 方法");}@AfterReturning("pt()")public void doAfterReturning() {log.info("执行 AfterReturning 方法");}@AfterThrowing("pt()")public void doAfterThrowing() {log.info("执行 AfterThrowing 方法");}@Around("pt()")public Object doAround(ProceedingJoinPoint pjp) throws Throwable {log.info("Around 方法开始执行");Object result = pjp.proceed();log.info("Around 方法执行后");return result;}
}

在这里插入图片描述

我们这里 @PointCut 注解的方法的修饰限定词是 private,该切点表达式只能在当前类中使用,如果我们想要在当前项目的其他切面类中使用这个提取出来的切点表达式的话,需要将方法的修饰限定词改为 public,并且引用这个切点表达式的方法需要使用 全限定类名.方法名()

@Pointcut("execution(* com.example.springaop.controller.*.*(..))")
public void pt(){}
@Slf4j
@Component
@Aspect
public class AspectDemo2 {@Before("com.example.springaop.aspect.AspectDemo.pt()")public void deBefore() {log.info("执行 AspectDemo2 中的 Before 方法");}
}

在这里插入图片描述

4.4 切面优先级 @Order注解

当存在多个切面的时候,并且这些切面类的多个切入点都匹配到了同一个目标方法,当目标方法运行的时候,这些切面类中的通知方法都会执行,这些通知方法的执行顺序会遵守两个原则:

  1. 前面通知类型的先后顺序
  2. 切面类的类名排序

我们创建出三个切面类,来看多个切面类匹配到了同一个目标方法之后的通知方法的执行顺序:

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
通过这个运行结果可以知道,当存在多个切面类时,默认按照切面类名字母排序:

  • @Before 通知:字母排名靠前的先执行
  • @After 通知,字母排名靠前的后执行

就可以形象的看成这个模型:

在这里插入图片描述
但是根据切面类的类名字母排序的话,很不方便,所以为了切面类执行顺序更好的控制,就出现了一个注解 @Order() 用来控制多个切面类的执行顺序。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

可以看到,通过使用 @Order 注解可以控制多个切面类的执行顺序。

4.5 切点表达式

前面我们使用切点表达式来描述切点,接下来我们来详细介绍一下切点表达式的语法。

切点表达式常见的有两种表达方式:

  1. execution(…):根据方法的签名来匹配
  2. @annotation(…):根据注解匹配

4.5.1 execution 切点表达式

execution() 是最常用的切点表达式,用来匹配方法,它的语法为:

execution(<访问修饰符> <返回类型> <包名.类名.方法(方法参数)> <异常>)

其中访问修饰符和异常可以省略。

在这里插入图片描述
切点表达式支持通配符表达:

  1. *:匹配任意字符,只匹配一个元素(返回类型,包,类名,方法或者方法参数)
  • 包名使用 * 表示任意包(一层包使用一个*)
  • 类名使用 * 表示任意类
  • 返回值使用 * 表示任意返回值类型
  • ⽅法名使用 * 表示任意⽅法
  • 参数使用 * 表示⼀个任意类型的参数
  1. … :匹配多个连续的任意符号,可以通配任意层级的包,或任意类型,任意个数的参数
  • 使用 … 配置包名,表示此包以及此包下的所有子包
  • 可以使用 … 配置参数,任意个任意类型的参数

示例:

TestController 下的 public修饰,返回类型为 String 方法名为 t1,⽆参⽅法

execution(public String com.example.demo.controller.TestController.t1())

省略访问修饰符:

execution(String com.example.demo.controller.TestController.t1())

匹配所有返回类型:

execution(* com.example.demo.controller.TestController.t1())

匹配 TestController 下的所有无参方法:

execution(* com.example.demo.controller.TestController.*())

匹配 TestController 下的所有方法:

execution(* com.example.demo.controller.TestController.*(..))

匹配 controller 包下所有的类的所有方法:

execution(* com.example.demo.controller.*.*(..))

匹配所有包下⾯的 TestController:

execution(* com..TestController.*(..))

匹配 com.example.demo 包下,子孙包下的所有类的所有⽅法:

execution(* com.example.demo..*(..))

4.5.2 @annotation

execution 表达式适用于更符合规则的,如果我们要匹配更多无规则的方法的话,使用 execution 表达式就会很吃力。

在这里插入图片描述

假设我需要捕获到 t2 和 t3 方法,使用 execution 表达式该如何写呢?因为这两个方法的返回值不同,使用 execution 表达式无法同时表示出这两个方法。所以这时候就需要使用 @annotation 注解来捕获到更多无规则的方法。

4.5.2.1 自定义注解

如何使用 @annotaion 呢?首先我们需要自定义出注解。

在新建 Java class 的时候选择 Annotation:

在这里插入图片描述

然后里面的内容我们就实现一个最简单的注解,参考 @Component 注解来构造一个自定义的注解:

在这里插入图片描述

@Target 标识了 Annotation 所修饰的对象范围,即该注解可以用在什么地方:

  • ElementType.TYPE:⽤于描述类、接⼝(包括注解类型)或enum声明
  • ElementType.METHOD:描述方法
  • ElementType.PARAMETER:描述参数
  • ElementType.TYPE_USE:可以标注任意类型

@Retention 指Annotation 被保留的时间长短,标明注解的⽣命周期:

  1. RetentionPolicy.SOURCE:表示注解仅存在于源代码中,编译成字节码后会被丢弃。这意味着在运行时无法获取到该注解的信息,只能在编译时使⽤。比如 @SuppressWarnings ,以及lombok提供的注解 @Data ,@Slf4j
  2. RetentionPolicy.CLASS:编译时注解。表示注解存在于源代码和字节码中,但在运行时会被丢弃。这意味着在编译时和字节码中可以通过反射获取到该注解的信息,但在实际运行时⽆法获取。通常⽤于⼀些框架和⼯具的注解
  3. RetentionPolicy.RUNTIME:运行时注解。表示注解存在于源代码,字节码和运行时中。这意味着在编译时,字节码中和实际运行时都可以通过反射获取到该注解的信息。通常⽤于⼀些需要在运行时处理的注解,如Spring的 @Controller @ResponseBody

我们一般就使用 RUNTIME 来指明注解的存在时间。

package com.example.springaop.aspect;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAspect {
}
4.5.2.2 切面类

使用 @annotation 切点表达式定义切点,只对 @MyAspect ⽣效:

@annotation 中需要填入我们自定义注解的全限定名。

@Slf4j
@Component
@Aspect
public class MyAspectDemo {@Before("@annotation(com.example.springaop.aspect.MyAspect)")public void before() {log.info("执行 MyAspectDemo 的 Before 方法");}@After("@annotation(com.example.springaop.aspect.MyAspect)")public void after() {log.info("执行 MyAspect 的 After 方法");}
}
4.5.2.3 添加自定义注解
@Slf4j
@RestController
@RequestMapping("test")
public class TestController {@RequestMapping("/t1")public String t1() {log.info("目标方法执行");return "hello";}@MyAspect@RequestMapping("/t2")public String t2() {log.info("我爱Java,我要成为Java高手");return "hello world";}@MyAspect@RequestMapping("/t3")public Integer t3() {log.info("目标方法2执行");Integer result = 10/0;return result;}
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
可以看到通过使用 @annotation 可以更加灵活的捕获到无规则的方法。

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【面试】测试/测开(ING3)

190. 栈和堆在内存管理上的区别 栈 1&#xff09; 栈是由系统自动分配和回收的内存。 2&#xff09;栈的存储地址是由高地址向低地址扩展的。 3&#xff09;栈是一个先进后出的结构。 4&#xff09;栈的空间大小是一个在编译时确定常数&#xff0c;即栈的大小是有限制的&#x…

一个 web版linux、数据库、redis、mongo统一管理操作平台

mayfly-go&#xff1a;一个 web版linux、数据库、redis、mongo统一管理操作平台 功能介绍 linux&#xff1a; ssh终端(终端操作记录回放)&#xff0c;文件查看&#xff08;可根据常见后缀名高亮显示关键词等&#xff09;、修改、上传、下载、删除等&#xff0c;脚本管理执行&…

C语言——小细节和小知识9

一、大小端字节序 1、介绍 在计算机系统中&#xff0c;大小端&#xff08;Endianness&#xff09;是指多字节数据的存储和读取顺序。它是数据在内存中如何排列的问题&#xff0c;特别是与字节顺序相关。C语言中的数据存储大小端字节序指的是在内存中存储的多字节数据类型&…

Unity之物理系统

专栏的上一篇角色控制器控制角色移动跳崖&#xff0c;这一篇来说说Unity的物理系统。 本篇小编还要带大家做一个碰撞检测效果实例&#xff0c;先放效果图&#xff1a;流星撞击地面产生爆炸效果 一、Rigidbody 我们给胶囊添加了 Rigidbody 组件它才有的重力&#xff0c;我们来…

QT上位机开发(进度条操作)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 进度条是一个比较常见的控件。如果某个操作需要很长的时间才能完成&#xff0c;那么这个时候最好有一个进度条提示&#xff0c;这样比较容易平复一…

网络端口映射和端口转发的区别和联系

目 录 一、端口映射技术 1.1 原理 1.2 应用场景 1、远程访问 2、游戏主机 3、文件共享 4、监控视频共享 二、端口转发技术 2.1 原理 2.2 应用场景 1、网络负载均衡 2、网络安全 3、网络代理 三、端口映射和转发的实现方法 3.1 路由器配置 3.2 网络防火墙 …

Jupyter Notebook五分钟基础速通

1 作用 常用于数据分析 2 安装 2.1 Anaconda 通过直接安装Anaconda&#xff0c;会自动安装Jupyter Notebook 2.2 命令行安装 ① 3.x版本 pip3 install --upgrade pip pip3 install jupyter ② 2.x版本 pip install --upgrade pip pip install jupyter 3 启动 cmd窗口下…

主动自动智能、全天在线值守!浪潮信息InService托维服务平台V2.0焕新升级

日前&#xff0c;浪潮信息发布了InService托维服务平台2.0版本&#xff0c;新增了自动化微码版本推荐、硬盘故障智能预测、服务报告自助订阅等功能模块。该平台对接入设备可7*24小时在线监测&#xff0c;量化评估设备状态&#xff0c;提前预警故障&#xff0c;实现主动式服务。…

Android Matrix绘制PaintDrawable设置BitmapShader,手指触点为圆心scale放大原图,Kotlin

Android Matrix绘制PaintDrawable设置BitmapShader&#xff0c;手指触点为圆心scale放大原图&#xff0c;Kotlin 在 Android基于Matrix绘制PaintDrawable设置BitmapShader&#xff0c;以手指触点为中心显示原图的圆切图&#xff0c;Kotlin&#xff08;4&#xff09;-CSDN博客 的…

【计算机网络】【新加坡南洋理工大学】【Computer Control Network】【广域网和局域网简介】【中英对照(自译)】

一、说明 仅供学习使用。 二、广域网&#xff08;WAN&#xff09;和局域网&#xff08;LAN&#xff09;简介

【文档数据库】ES和MongoDB的对比

目录 1.由文档存储牵出的问题 2.什么是MongoDB&#xff1f; 3.ES和MongoDB的对比 1.由文档存储牵出的问题 本文或者说关于mongodb的这个系列文章的源头&#xff1a; 前面我们聊过了分布式链路追踪系统&#xff0c;在基于日志实现的分布式链路追踪的方式seluthzipkin中为了…

企业如何在新媒体领域做好风险管理?

为了企业能更加放心地进行新媒体营销活动&#xff0c;矩阵通向企业提供一套完整的新媒体风险管控机制&#xff0c;支持企业新媒体矩阵账号体检、敏感内容违规检测、实时监测多平台负面用户信息。 矩阵通是新榜旗下的新媒体数字化内容资产管理SaaS&#xff0c;可以帮助企业解决跨…