Spring之AOP的详细讲解

      

目录

一.SpringAOP是什么?

1.1理论知识点

1.2简单的AOP例子

二.SpringAOP的核心概念 

2.1切点(Pointcut)

2.2通知(Advice)

2.3切⾯(Aspect)

2.4通知类型

2.5切⾯优先级 @Order

2.6切点表达式

2.6.1 @execution表达式

2.6.2@annotation表达式

总结


一.SpringAOP是什么?

1.1理论知识点

        在学习SpringAOP前,我们需要了解一下什么是AOP?

         AOP(Aspect Oriented Programming):⾯向切⾯编程,通过预编译和运行期间动态代理来实现程序功能的统一维护的一种技术。 它是⼀种思想,它是对某⼀类事情的集中处理。
        ⽐如⽤户登录权限的效验,没学 AOP 之前,我们所有需要判断⽤户登录的⻚⾯(中
的⽅法),都要各⾃实现或调⽤⽤户验证的⽅法,然⽽有了 AOP 之后,我们只需要在某⼀处配置⼀下,所有需要判断⽤户登录⻚⾯(中的⽅法)就全部可以实现⽤户登录验证了,不再需要每个⽅法中都写相同的⽤户登录验证了。

        AOP中的基本单元是 Aspect(切面)

1.2简单的AOP例子

        理论永远没有代码直观!

引入依赖:

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

定义切面: 

@Aspect // 定义切面
@Component
public class UserAspect {// 切点@Pointcut("execution(* com.example.interview.Controller.UserController.*(..))")public void pointcut() {}// 前置通知通知@Before("pointcut()")public void doBefore() {System.out.println("执行了前置通知");}// 后置通知@After("pointcut()")public void doAfter() {System.out.println("执行了后置通知");}// 环绕通知@Around("pointcut()")public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {System.out.println("环绕通知执行之前");// 执行目标方法Object result = joinPoint.proceed();System.out.println("环绕通知执行之后");return result;}}

设计的Controller类:

package com.example.interview.Controller;import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
@RequestMapping("/user")
public class UserController {@RequestMapping("/getuser")public String getUser(){System.out.println("do getUser");return "get user";}@RequestMapping("/deluser")public String delUser(){System.out.println("do delUser");return "del user";}}

执行结果:

二.SpringAOP的核心概念 

我们接下来分析一下切面代码:

从上面的代码中,我们可以得到哪些要素呢?

  • @Aspect:切面类,告诉Spring我这个类是个切面,里面有特殊处理方法
  • @Pointcut:切点,告诉Spring我要针对什么
  • @Before、@Around、@AfterReturning、@After、@AfterThrowing:通知,告诉Spring针对后要做什么处理

2.1切点(Pointcut)

切点(Pointcut), 也称之为"切⼊点"
Pointcut 的作⽤就是提供⼀组规则 (使⽤ AspectJ pointcut expression language 来描述), 告诉程序对 哪些⽅法来进⾏功能增强.也称:公共切点表达式!

如果我们不使用@Pointcut注释,将会让代码冗余大量的切点表达式!

不使用情况下:

@Aspect // 定义切面
@Component
public class UserAspect {// 前置通知通知@Before("execution(* com.example.interview.Controller.UserController.*(..))")public void doBefore() {System.out.println("执行了前置通知");}// 后置通知@After("execution(* com.example.interview.Controller.UserController.*(..))")public void doAfter() {System.out.println("执行了后置通知");}// 环绕通知@Around("execution(* com.example.interview.Controller.UserController.*(..))")public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {System.out.println("环绕通知执行之前");// 执行目标方法Object result = joinPoint.proceed();System.out.println("环绕通知执行之后");return result;}}

我们会发现存在⼤量重复的切点表达 execution(*com.example.interview.Controller.UserController.*(..))")

execution,也可以说是连接点,就是告诉Spring,该路径下需要控制的方法,*代表的是所有方法,(..)代表任意参数。

注: 当切点定义使⽤private修饰时, 仅能在当前切⾯类中使⽤, 当其他切⾯类也要使⽤当前切点定义时, 就需要把private改为public. 引⽤⽅式为: 全限定类名.⽅法名()

例如:

@Slf4j
@Aspect
@Component
public class AspectDemo2 {//前置通知@Before("com.example.demo.aspect.AspectDemo.pt()")public void doBefore() {log.info("执⾏ AspectDemo2 -> Before ⽅法");}
}

2.2通知(Advice)

  • 通知包括前置通知、后置通知和环绕通知。
    • 前置通知在 doBefore() 方法中定义,使用了 @Before 注解,在切点方法执行之前被调用。
    • 后置通知在 doAfter() 方法中定义,使用了 @After 注解,在切点方法执行之后被调用。
    • 环绕通知在 doAround() 方法中定义,使用了 @Around 注解,在切点方法执行前后都可以进行一些额外的处理。环绕通知方法的参数类型为 ProceedingJoinPoint,可以通过调用 proceed() 方法执行目标方法,并在执行前后进行其他操作。

例如: 

2.3切⾯(Aspect)

       注: 切⾯(Aspect) = 切点(Pointcut) + 通知(Advice)
就是整个代码全是切面的知识点

2.4通知类型

Spring中AOP的通知类型有以下⼏种:
  • @Around: 环绕通知, 此注解标注的通知⽅法在⽬标⽅法前, 后都被执⾏
  • @Before: 前置通知, 此注解标注的通知⽅法在⽬标⽅法前被执⾏
  • @After: 后置通知, 此注解标注的通知⽅法在⽬标⽅法后被执⾏, ⽆论是否有异常都会执⾏
  • @AfterReturning: 返回后通知, 此注解标注的通知⽅法在⽬标⽅法后被执⾏, 有异常不会执⾏
  • @AfterThrowing: 异常后通知, 此注解标注的通知⽅法发⽣异常后执⾏

        前面五种,我们都可以通过之前代码看出,但是第五种是异常通知,程序正常运⾏的情况下, @AfterThrowing 标识的通知⽅法不会执⾏。但是如果发生异常了呢?什么会执行,上面不会执行呢?

  • @AfterReturning 标识的通知⽅法不会执⾏, @AfterThrowing 标识的通知⽅法执⾏了
  •   @Around 环绕通知中原始⽅法调⽤时有异常,通知中的环绕后的代码逻辑也不会在执⾏了(因为原始⽅法调⽤出异常了)

2.5切⾯优先级 @Order

        当我们在⼀个项⽬中, 定义了多个切⾯类时, 并且这些切⾯类的多个切⼊点都匹配到了同⼀个⽬标⽅法. 当⽬标⽅法运⾏的时候, 这些切⾯类中的通知⽅法都会执⾏, 那么这⼏个通知⽅法的执⾏顺序是什么样的呢?

切面定义三个,分别为AspectDemo2、AspectDemo3、AspectDemo4,为了简易化,只写@Before和@After,而这里只展示一个代码,其他在修改一下类名即可:

@Aspect // 定义切面
@Component
public class AspectDemo2 {@Pointcut("execution(* com.example.interview.Controller.UserController.*(..))")private void pt(){}// 前置通知通知@Before("pt()")public void doBefore() {System.out.println("执行了前置通知2");}// 后置通知@After("pt()")public void doAfter() {System.out.println("执行了后置通知2");}
}

UserControer代码:

@RestController
@RequestMapping("/user")
public class UserController {@RequestMapping("/getuser")public String getUser(){System.out.println("do getUser");return "get user";}@RequestMapping("/deluser")public String delUser(){System.out.println("do delUser");return "del user";}}

访问对应的接口程序:http://localhost:8080/user/getuser

运行结果如下图:

通过对比我们可以发现:存在多个切⾯类时, 默认按照切⾯类的类名字⺟排序:

  •  @Before 通知:字⺟排名靠前的先执⾏
  •  @After 通知:字⺟排名靠前的后执⾏

问:如果我们需要指定某个切面先执行呢?

答: Spring 给我们提供了⼀个新的注解, 来控制这些切⾯通知的执⾏顺序: @Order

使用方式如下:

我们在切面类AspectDemo2、AspectDemo3、AspectDemo4上分别加上注解:@Order(3)、@Order(2)、@Order(1).

例如:

@Aspect // 定义切面
@Component
@Order(1)
public class AspectDemo4 {//代码照旧
}

 访问对应的接口程序:​​​​​​http://localhost:8080/user/getuser

运行结果: 

通过上述程序的运⾏结果, 得出结论:
@Order 注解标识的切⾯类, 执⾏顺序如下:
  • @Before 通知:数字越⼩先执⾏
  • @After 通知:数字越⼤先执⾏
@Order 控制切⾯的优先级, 先执⾏优先级较⾼的切⾯, 再执⾏优先级较低的切⾯, 最终执⾏⽬标⽅法.

2.6切点表达式

切点表达式常⻅有两种表达⽅式
  1.  execution(……):根据⽅法的签名来匹配
  2.  @annotation(……) :根据注解匹配

2.6.1 @execution表达式

execution() 是最常⽤的切点表达式, ⽤来匹配⽅法, 语法为:
 execution(<访问修饰符> <返回类型> <包名.类名.⽅法(⽅法参数)> <异常>)

切点表达式⽀持通配符表达:
  1. * :匹配任意字符,只匹配⼀个元素(返回类型, 包, 类名, ⽅法或者⽅法参数)
    1.  包名使⽤ * 表⽰任意包(⼀层包使⽤⼀个*)
    2.  类名使⽤ * 表⽰任意类
    3.  返回值使⽤ * 表⽰任意返回值类型
    4. ⽅法名使⽤ * 表⽰任意⽅法
    5. 参数使⽤ * 表⽰⼀个任意类型的参数
  2. .. :匹配多个连续的任意符号, 可以通配任意层级的包, 或任意类型, 任意个数的参数
    •  使⽤ .. 配置包名,标识此包以及此包下的所有⼦包
    • 可以使⽤ .. 配置参数,任意个任意类型的参数

2.6.2@annotation表达式

        execution表达式更适⽤有规则的, 如果我们要匹配多个⽆规则的⽅法呢, 
问:如果我们 匹配两个不同类的一个方法,怎么操作呢?
我们可以借助⾃定义注解的⽅式以及另⼀种切点表达式 @annotation 来描述这⼀类的切点

第一步准备测试方法:

@RequestMapping("/test")
@RestController
public class TestController {@RequestMapping("/t1")public String t1() {return "t1";}@RequestMapping("/t2")public boolean t2() {return true;}
}
@RequestMapping("/user")
@RestController
public class UserController {@RequestMapping("/u1")public String u1(){return "u1";}@RequestMapping("/u2")public String u2(){return "u2";}
}

第二步自定义注解@MyAspect

代码内容:

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 {}

注解解释:

一.@Target 标识了 Annotation 所修饰的对象范围, 即该注解可以⽤在什么地⽅. 常⽤取值:
  • ElementType.TYPE: ⽤于描述类、接⼝(包括注解类型) 或enum声明
  • ElementType.METHOD: 描述⽅法
  • ElementType.PARAMETER: 描述参数
  • ElementType.TYPE_USE: 可以标注任意类型
二. @Retention 指Annotation被保留的时间⻓短, 标明注解的⽣命周期,@Retention 的取值有三种:
  • RetentionPolicy.SOURCE:表⽰注解仅存在于源代码中, 编译成字节码后会被丢弃. 这意味着在运⾏时⽆法获取到该注解的信息, 只能在编译时使⽤. ⽐如 @SuppressWarnings , 以及 lombok提供的注解 @Data , @Slf4j
  • RetentionPolicy.CLASS:编译时注解. 表⽰注解存在于源代码和字节码中, 但在运⾏时会被丢弃. 这意味着在编译时和字节码中可以通过反射获取到该注解的信息, 但在实际运⾏时⽆法获 取. 通常⽤于⼀些框架和⼯具的注解.
  • RetentionPolicy.RUNTIME:运⾏时注解. 表⽰注解存在于源代码, 字节码和运⾏时中. 这意味着在编译时, 字节码中和实际运⾏时都可以通过反射获取到该注解的信息. 通常⽤于⼀些需要 在运⾏时处理的注解, 如Spring的 @Controller @ResponseBody

第三步:切面类定义,将@execution修改为@annotation  ,但是目标源为自定义的注解@MyAspect

@Slf4j
@Component
@Aspect
public class MyAspectDemo {//前置通知@Before("@annotation(com.example.demo.aspect.MyAspect)")public void before(){log.info("MyAspect -> before ...");}//后置通知@After("@annotation(com.example.demo.aspect.MyAspect)")public void after(){log.info("MyAspect -> after ...");}
}

第四步:在测试方法当中添加自定义的注解--@MyAspect

@MyAspect
@RequestMapping("/t1")
public String t1() {return "t1";
}@MyAspect
@RequestMapping("/u1")
public String u1(){return "u1";
}

第五步,访问

http://127.0.0.1:8080/test/t1, 切⾯通知执⾏
http://127.0.0.1:8080/user/u1 , 切⾯通知执⾏.
未添加注解:
http://127.0.0.1:8080/test/t2, 切⾯未通知执⾏
http://127.0.0.1:8080/user/u2 , 切⾯未通知执⾏.

总结

        Spring AOP(Aspect-Oriented Programming,面向切面编程)是 Spring 框架中的一个模块,用于实现横切关注点的模块化开发。代理是 Spring AOP 实现的一种方式。

        在 Spring AOP 中,代理是实现切面的一种方式之一。通过代理,Spring AOP 可以在目标对象的方法执行前、执行后或抛出异常时,执行额外的逻辑(如日志记录、性能监控、事务管理等)。Spring AOP 使用代理机制来实现横切关注点的织入。 

Spring AOP 实现代理的方式有两种:

  1. 基于 JDK 动态代理: 如果目标对象实现了至少一个接口,Spring AOP 就会使用 JDK 动态代理来为目标对象创建代理。在运行时,Spring AOP 会动态生成一个实现了目标对象所有接口的代理对象,并在代理对象的方法中织入切面逻辑。

  2. 基于 CGLIB 代理: 如果目标对象没有实现任何接口,Spring AOP 就会使用 CGLIB(Code Generation Library)来为目标对象创建代理。CGLIB 使用字节码生成技术,在运行时生成目标对象的子类,并重写其中的方法来织入切面逻辑。

详细见此章:http://t.csdnimg.cn/iAZZG

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

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

相关文章

【MYSQL】MySQL整体结构之系统服务

一、系统服务层 学习了MySQL网络连接层后&#xff0c;接下来看看系统服务层&#xff0c;MySQL大多数核心功能都位于这一层&#xff0c;包括客户端SQL请求解析、语义分析、查询优化、缓存以及所有的内置函数&#xff08;例如&#xff1a;日期、时间、统计、加密函数...&#xff…

SAP SD学习笔记07 - 紧急发注(急单),现金贩卖,贩卖传票Type/ 明细Category 及其Customize

上面讲SAP中主干流程的时候&#xff0c;还有后面讲一括处理的时候&#xff0c;都用的是 OR 标准受注。 SAP SD学习笔记01 - 简单走一遍SD的流程&#xff1a;受注&#xff0c;出荷&#xff0c;请求_怎么学好sd模块-CSDN博客 下面开始讲一些稀奇古怪的非标准流程。 当然&#x…

【竞技宝jjb.lol】LOL:T1成功击败HLE晋级MSI!

北京时间2024年4月13日,英雄联盟LCK2024春季季后赛继续进行,昨天迎来败者组决赛HLE对阵T1。本场比赛HLE率先拿下一局之后,T1连续两局在后期决策上碾压HLE拿到赛点,第四局zeus祭出上单VN在中期杀穿HLE后排,最终T1以3-1的比分击败HLE晋级春季决赛,同时也拿到了MSI的参赛资格。以下…

第 128 场 LeetCode 双周赛题解

A 字符串的分数 模拟 class Solution {public:int scoreOfString(string s) {int res 0;for (int i 1; i < s.size(); i) res abs(s[i] - s[i - 1]);return res;} };B 覆盖所有点的最少矩形数目 排序&#xff1a;先按照 x i x_i xi​ 排序&#xff0c;然后顺序遍…

计算机体系架构

冯诺依曼架构 我们编写的程序存储在哪里呢&#xff1f;CPU内部的结构其实很简单&#xff0c;除了ALU、控制单元、寄存器和少量Cache&#xff0c;根本没有多余的空间存放我们编写的代码&#xff0c;我们需要额外的存储器来存放我们编写的程序&#xff08;指令序列&#xff09;。…

【鸿蒙开发】第二十章 Camera相机服务

1 简介 开发者通过调用Camera Kit(相机服务)提供的接口可以开发相机应用&#xff0c;应用通过访问和操作相机硬件&#xff0c;实现基础操作&#xff0c;如预览、拍照和录像&#xff1b;还可以通过接口组合完成更多操作&#xff0c;如控制闪光灯和曝光时间、对焦或调焦等。 2 …

【Altium Designer 20 笔记】隐藏PCB上的信号线(连接线)

使用网络类隐藏特定类型的信号线 如果你想要隐藏特定类型的信号线&#xff08;例如电源类&#xff09;&#xff0c;你可以首先创建一个网络类。使用快捷键DC调出对象类浏览器&#xff0c;在Net Classes中右击添加类&#xff0c;并重命名&#xff08;例如为“Power”&#xff0…

第1章、react基础知识;

一、react学习前期准备&#xff1b; 1、基本概念&#xff1b; 前期的知识准备&#xff1a; 1.javascript、html、css&#xff1b; 2.构建工具&#xff1a;Webpack&#xff1a;https://yunp.top/init/p/v/1 3.安装node&#xff1a;npm&#xff1a;https://yunp.top/init/p/v/1 …

EasyPoi表格导入添加校验

EasyPoi表格导入添加校验 项目添加maven依赖实体类自定义校验controller测试结果 项目添加maven依赖 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2…

欧美地区媒体发稿:8个高效率方式分析提高知名度

1.媒体发稿推广的意义媒体发稿营销推广是指由企业和产品的信息传递给目标群体的一个过程。在欧美地区&#xff0c;媒体发稿营销推广针对企业品牌建设与市场开拓尤为重要。根据媒体发稿营销推广&#xff0c;企业能够提高知名度&#xff0c;提升曝光度&#xff0c;提升销售。 2.寻…

CentOS7安装node-v20.12.2

个人记录 官网查看最新版本 NodeJs下载地址 进入指定目录 cd /usr/local/下载 wget https://nodejs.org/dist/v20.12.2/node-v20.12.2-linux-x64.tar.xz --no-check-certificate解压 tar -xvf node-v20.12.2-linux-x64.tar.xz查看 ls ls node-v20.12.2-linux-x64编辑配…

如何发现高危的PoC和EXP?漏洞检测方法 示例,实战应急实战举例,包括:SQLi、XSS、SSTI/ELI、文件哈希、SSRF、命令执行/命令注入等等

如何发现高危的PoC和EXP?漏洞检测方法 & 示例,实战应急实战举例,包括:SQLi、XSS、SSTI/ELI、文件哈希、SSRF、命令执行/命令注入等等。 在网络安全领域,发现高危的PoC(Proof of Concept)和EXP(Exploit)对于防范和应对潜在的安全威胁至关重要。以下是关于如何发现高…