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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

PHP 伪协议:使用 php://input 访问原始 POST 数据

文章目录 参考环境PHP 伪协议概念为什么需要 PHP 伪协议&#xff1f; php://input为什么需要 php://input&#xff1f;更灵活的数据处理减小性能压力 发送 POST 数据HackBarHackBar 插件的获取 $_POST打开 HackBar 插件通过 HackBar 插件发起 POST 请求 基操 enable_post_data_…

【ros】结果实时在线可视化

文章目录 一、前言二、订阅与发布三、回调四、可视化 4.1、初始化参数4.2、初始化图片 4.3、画结果 4.4、可视化结果 一、前言 感知与规划控制是无人驾驶算法重要算法&#xff0c;在交付测试阶段也最容易引起摩擦&#xff0c;这也是司空见惯的现象。有时候可能是接口对齐问题…

AI绘画与建筑大师共创出的作品,震惊了?!

在CAD制图盛行的今天&#xff0c;手绘依然是许多建筑大师首选的灵感记录方式。建筑大师西扎曾说过&#xff1a;草图能迅速的记录下他思维的瞬间&#xff0c;并再一次激发他更深入的思考。 看完这些建筑大师的手稿&#xff0c;不得不让人表示&#xff1a;这和医生处方手迹简直有…

【满满干货】聚合接口—自动化工具㊣

背景 在介绍接口自动化之前先给大家分享一下我所理解的“业务中台”的概念&#xff1a;业务中台是将企业的核心能力以数字化形式沉淀为各种服务中心&#xff0c;其目的是“提供企业能够快速&#xff0c;低成本创新的能力”。 例如公司内部的业务a、业务b同时有订单、登录等功…

企业网盘私有化部署和本地私有化部署的区别

在当今数据量激增的背景下&#xff0c;企业如何高效、安全地管理和传输大量数据成为了一个关键问题。企业网盘作为一种解决方案&#xff0c;其部署方式直接影响到数据的安全性、工作效率的提升以及运营成本的控制。私有化部署与本地化部署是两种主流的企业网盘部署策略&#xf…

C语言_文件操作

文件基础 什么是文件 文件是在计算机中以实现某种功能、或某个软件的部分功能为目的而定义的一个单位。磁盘上的文件是文件。但是在程序设计中&#xff0c;我们一般谈的文件有两种&#xff1a;程序文件、数据文件&#xff08;从文件功能的角度来分的&#xff09;。 程序文件 …

【MATLAB源码-第33期】matlab基于遗传算法的多层编码柔性作业车间调度问题仿真

操作环境&#xff1a; MATLAB 2022a 1、算法描述 1. 遗传算法&#xff1a; 遗传算法是一种基于自然选择和遗传遗传学的优化算法。它模拟了生物进化的过程&#xff0c;通过对问题解的编码&#xff08;通常以染色体或基因型的形式&#xff09;、交叉、变异等操作来生成新的解。…

Coze 识别用户意图

文章目录 Coze 识别用户意图 Coze 识别用户意图 本文将通过 LLM 节点、Condition 节点和插件节点构建一个用于识别用户意图的工作流。 效果示例 本文构建的示例工作流概览如下。 在该工作流中&#xff1a; 使用 LLM 节点将用户输入数据分为 1&#xff08;天气&#xff09;、…

Flume实时读取目录文件到HDFS案例

【尚硅谷】大数据技术之Flume教程从入门到实战_哔哩哔哩_bilibili 目录 flume简介 flume案例 1、监控端口数据官方案例 2、实时读取目录文件到HDFS案例 flume简介 Flume是Cloudera提供的一个高可用的&#xff0c;高可靠的&#xff0c;分布式的海量日志采集、聚合和传输的系…

【UE Niagara】烟雾特效

效果 步骤 1. 创建一个材质&#xff0c;这里命名为“M_Smoke” 设置混合模式为半透明&#xff0c;着色模型为无光照 连接如下节点 其中纹理采样节点所使用的纹理为引擎自带的“T_SmokeSubUV_8x8” 2. 新建一个Niagara发射器&#xff0c;模板使用“Empty”&#xff0c;这里命名…

MLeaksFinder报错

1.报错&#xff1a;FBClassStrongLayout.mm 文件&#xff1a;layoutCache[currentClass] ivars; 解决&#xff1a;替换为layoutCache[(id)currentClass] ivars; 2.编译正常但运行时出现crash indirect_symbol_bindings[i] cur->rebinding FBRetainCycleDetector iOS15 …

亚马逊运营必看!如何运用自养号测评获得买家评论转销量?

作为亚马逊卖家&#xff0c;相信大家对亚马逊的产品星级评分 (Rating) 都不陌生&#xff0c;这几颗亮眼的星星&#xff0c;不仅可以让你的Listing脱颖而出&#xff0c;获得足够多、足够高的产品评分&#xff0c;也是促使消费者下单的重要因素之一。 那么&#xff0c;亚马逊运营…

DepthFormer论文详解

摘要 本文旨在解决有监督单目深度估计的问题&#xff0c;我们从一项细致的试点研究开始&#xff0c;以证明远程相关性对于准确的深度估计至关重要。我们建议使用Transformer以有效地注意力机制对这种全局上下文进行建模。我们还采用一个额外的卷积分支来保留局部信息&#xff0…

NPU编译MultiScaleDeformableAttention

NPU对pytorch&#xff0c;想将检测模型在NPU上训练&#xff0c;存在编译MultiScaleDeformableAttention的需求。 然而&#xff0c;原dino模型https://github.com/IDEA-Research/DINO/tree/main/models/dino/ops/src 仅包含CPU版本和GPU版本&#xff1a; 是不是就真的无法解决…

2024/4/5—力扣—在排序数组中查找元素的第一个和最后一个位置

代码实现&#xff1a; 思路&#xff1a;二分法 方法一&#xff1a;分别查找左右侧边界 /*** Note: The returned array must be malloced, assume caller calls free().*/ int GetTargetFirstPosition(int *nums, int numsSize, int target) {int l 0, r numsSize - 1;while …

【北京迅为】《iTOP-3588开发板开发板系统编程手册》第3章 标准IO

RK3588是一款低功耗、高性能的处理器&#xff0c;适用于基于arm的PC和Edge计算设备、个人移动互联网设备等数字多媒体应用&#xff0c;RK3588支持8K视频编解码&#xff0c;内置GPU可以完全兼容OpenGLES 1.1、2.0和3.2。RK3588引入了新一代完全基于硬件的最大4800万像素ISP&…

蓝桥杯复习笔记

文章目录 gridflexhtml表格合并单元格 表单表单元素input类型 select h5文件上传拖拽apiweb Storage css块元素和行内元素转换positionfloat溢出显示隐藏外边距过渡和动画动画变形选择器属性选择伪类选择器 css3边框圆角边框阴影渐变text-overflow与word-wrap jsdom操作documen…

STL容器之unordered_set类

文章目录 STL容器之unordered_set类1、unordered系列关联式容器2、unordered_set2.1、unordered_set介绍2.2、unordered_set的使用2.2.1、unordered_set的常见构造2.2.2、unordered_set的迭代器2.2.3、unordered_set的容量2.2.4、unordered_set的增删查2.2.5、unordered_set的桶…

C++--this指针

this 指针是一个隐含于每一个成员函数中的特殊指针。它是指向一个正操作该成员函数的对象。当对一个对象调用成员函数时&#xff0c;编译程序先将对象的地址赋予this指针&#xff0c;然后调用成员函数。每次成员函数存取数据成员时&#xff0c;C编译器将根据 this 指针所指向的…

由于找不到msvcp100.dll,无法继续执行代码要如何处理?正确的msvcp100.dll修复

由于找不到msvcp100.dll,无法继续执行代码要如何处理&#xff1f;其实要处理这种dll文件丢失的问题&#xff0c;还是比较简单的&#xff0c;只要我们了解清楚这个msvcp100.dll文件&#xff0c;那么就可以快速的解决&#xff0c;好了&#xff0c;废话不多说&#xff0c;我们一起…