Spring之AOP的详细讲解

news2024/11/26 14:31:59

      

目录

一.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.coloradmin.cn/o/1582521.html

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

相关文章

【绘图案例-开启图片类型的上下文withOptions Objective-C语言】

一、上午呢,我们讲了一下图片类型的上下文 1.开启图片类型的上下文:UIGraphicsBeginImageContext, 然后,我们在上边儿,画了一些东西, 然后呢,把它取出来了,通过UIGraphicsGetImageFromCurrentImageContext() 通过这个图片类型的上下文,取出来了一个image对象, …

邦火策划真的靠谱吗?餐饮品牌策划实例解析

邦火策划在餐饮品牌策划领域的表现是否靠谱&#xff0c;可以通过具体的实例来进行解析。以下是一些相关的实例分析&#xff0c;以探讨邦火策划在餐饮品牌策划方面的真实效果和专业性。 首先&#xff0c;从品牌塑造与传播的角度来看&#xff0c;邦火策划注重通过精准的市场定位…

element-plus报错TypeError: data.includes is not a function

发生在vue3中&#xff0c;页面报错。 查找原因&#xff0c;表格中的data初始值定义不是数组 改一下 再试就好了

【面试精讲】MyBatis设计模式及源码分析,MyBatis设计模式实现原理

【面试精讲】MyBatis设计模式及源码分析&#xff0c;MyBatis设计模式实现原理 目录 本文导读 一、MyBatis中运用的设计模式详解 1. 工厂模式&#xff08;Factory Pattern&#xff09; 2. 单例模式&#xff08;Singleton Pattern&#xff09; 3. 建造者模式&#xff08;Bu…

GEE图表案例——不同区域各地类面积直方图分布图表(矢量面积叠加直方图图)

简介 在GEE中对不同区域面积统计的直方图绘制具体流程如下: 数据准备: 首先,需要准备用于面积统计的地理数据,可以是矢量数据,如行政边界、土地使用类型等。也可以是栅格数据,如分类结果、土地覆盖数据等。 区域划分: 根据需要统计的区域,将数据进行区域划分。可以使用…

电商技术揭秘七:搜索引擎中的SEO关键词策略与内容优化技术

文章目录 引言一、关键词策略1.1 关键词研究与选择1. 确定目标受众2. 使用关键词研究工具3. 分析搜索量和竞争程度4. 考虑长尾关键词5. 关键词的商业意图6. 创建关键词列表7. 持续监控和调整 1.2 关键词布局与密度1. 关键词自然分布2. 标题标签的使用3. 首次段落的重要性4. 关键…

00后应届毕业生面试要一万,面试官:不好意思,我们给不起

前几天&#xff0c;小编收到了即将毕业的表弟发来的私信&#xff0c;说他刚刚面试一家单位被刷了。表弟专业学的不错&#xff0c;人也十分聪明&#xff0c;家里人们都对这个面试结果感到吃惊&#xff0c;一问才知&#xff0c;被刷的理由很简单&#xff1a;开了10K的工资&#x…

FPGA笔试面试题目记录

1 logic utilization 题目&#xff1a;Rank the following operations from lowest utilization to highest. Assume that all variables are 32-bit integers,that the operations are implemented using LUTs ony and that the synthesiser will produce an optimal digital…

基于springboot实现医院管理系统项目【项目源码+论文说明】

基于springboot实现医院管理系统演示 摘要 随着信息互联网信息的飞速发展&#xff0c;医院也在创建着属于自己的管理系统。本文介绍了医院管理系统的开发全过程。通过分析企业对于医院管理系统的需求&#xff0c;创建了一个计算机管理医院管理系统的方案。文章介绍了医院管理系…

cesium 动态墙效果 电子围栏效果

一、扩展材质 /*** 动态墙材质* param {*} options* param {String} options.color 颜色* param {Number} options.duration 持续时间 毫秒* param {String} options.trailImage 贴图地址*/function DynamicWallMaterialProperty(options) {this._definitionChanged new Cesi…

软件设计—接口安全设计规范

1.token授权机制 2.https传输加密 3.接口调用防滥用 4.日志审计里监控 5.开发测试环境隔离&#xff0c;脱敏处理 6.数据库运维监控审计 软件项目相关全套精华资料包获取方式①&#xff1a;点我获取 获取方式②&#xff1a;本文末个人名片直接获取。

synchronized锁机制升级过程——面试题

1. 无锁状态 对象在没有被任何线程锁定时处于无锁状态。此时对象头中的锁标志位通常表示为无锁&#xff08;例如&#xff0c;标记字段的特定位组合表示无锁或偏向锁状态&#xff09;。 2. 偏向锁&#xff08;Biased Locking&#xff09; 初次获取&#xff1a;当线程首次获得…

SpringMVC--核心概念 / @RequestMapping注解

目录 1. 准备工作 1.1. 创建SpringMVC-demo02 子模块 1.2. 添加相关依赖 1.3. 设置 Maven 打包方式 1.4. 配置 web.xml 文件 1.4.1. 创建 web.xml 文件 1.4.2. 默认配置方式 1.4.3. 扩展配置方式 1.4.4. 注意点 1.5. 配置 Spring 文件 1.5.1. Thymeleaf视图解析器 …

知识图谱的最新进展与未来趋势

知识图谱的最新进展与未来趋势 一、引言 在过去的几年中&#xff0c;知识图谱已经从一个前沿的研究概念发展成为现代信息技术不可或缺的一部分。作为结构化知识的存储和表示形式&#xff0c;知识图谱通过组织信息和数据提供了深刻的洞见&#xff0c;它已被广泛应用于搜索引擎优…

HarmonyOS4-学习入门知识总结

简单的组件学习&#xff1a; /*** weip 自定义控件* 自定义构建函数也是需要充电的地方&#xff0c;分全局和局部的* 全局&#xff1a;需要添加function关键字 局部&#xff1a;不加function关键字* Styles function 自定义公共样式 分全局和局部* Extends(Text) 继承模式 只…

springboot 创建子模块时 pom 配置

创建子模块 2. 修改父模块 pom 文件 添加如下内容 <packaging>pom</packaging><modules><module>mybatisconf</module></modules>3. 修改子模块 pom 文件 <parent><groupId>com.vazquez</groupId><artifactId>bo…

wangEditor 测试环境对,但是生产环境无法显示

package.json 文件版本 "wangeditor": "4.3.0"开发环境 new Editor(#${this.id});出来的数据 正式环境 new Editor(#${this.id});出来的数据 原因&#xff1a; vue.config 文件 打包策略的时候 const assetsCDN {css: [https://lf6-cdn-tos.bytecd…

python中报错“ModuleNotFoundError: No module named ‘openpyxl‘”

python中报错“ModuleNotFoundError: No module named ‘openpyxl’” 问题描述 运行python代码时&#xff0c;报错“ModuleNotFoundError: No module named ‘openpyxl’” 解决方案&#xff1a; 可能是没有安装openpyxl&#xff0c; # 安装命令 pip install openpyxl #…

如何恢复未保存或删除/丢失的Word文件?

关闭 Word 应用程序而不保存文档&#xff1f;误删Word文档&#xff1f;许多用户会在不同的情况下丢失Word文档。如果不幸遇到此类问题&#xff0c;如何恢复已删除或未保存的 Word 文档&#xff1f;有一些方法可以恢复未保存/删除的文档。此外&#xff0c;您还可以使用Word文件恢…

【USB 】Jack (Female) Type A Connectors 2 Port Mounting Peg

1. NO BACK SHIELD&#xff0c;NO ARMS&#xff0c;FRONT GROUNDING ARMS 2. BACK SHIELD&#xff0c;NO FRONT GROUNDING ARMS 3. NO BACK SHIELD&#xff0c;FRONT GROUNDING ARMS 4. BACK SHIELD&#xff0c;FRONT GROUNDING ARMS