【 Spring AOP 】

news2024/11/25 22:56:05

文章目录

  • 一、什么是 Spring AOP?
  • 二、为什要⽤ AOP?
  • 三、AOP 的组成
  • 四、Spring AOP 的实现
  • 五、Spring AOP 实现原理

一、什么是 Spring AOP?

AOP(Aspect Oriented Programming):⾯向切⾯编程,它是⼀种思想,它是对某⼀类事情的
集中处理
。⽐如⽤户登录权限的效验,没学 AOP 之前,我们所有需要判断⽤户登录的⻚⾯(中的⽅法),都要各⾃实现或调⽤⽤户验证的⽅法,然⽽有了 AOP 之后,我们只需要在某⼀处配置⼀下,所有需要判断⽤户登录⻚⾯(中的⽅法)就全部可以实现⽤户登录验证了,不再需要每个⽅法中都写相同的⽤户登录验证了

AOP 是⼀种思想,⽽ Spring AOP 是⼀个框架,是对 AOP 思想的实现,它们的关系和 IoC与 DI 类似 !


二、为什要⽤ AOP?

想象⼀个场景,我们在做后台系统时,除了登录和注册等⼏个功能不需要做⽤户登录验证之外,其他⼏乎所有⻚⾯调⽤的前端控制器( Controller)都需要先验证⽤户登录的状态,那这个时候我们要怎么处理呢?

我们之前的处理⽅式是每个 Controller 都要写⼀遍⽤户登录验证,然⽽当你的功能越来越多,那么你要写的登录验证也越来越多,⽽这些⽅法⼜是相同的,这么多的⽅法就会增加代码修改和维护的成本。那有没有简单的处理⽅案呢?

答案是肯定的,对于这种功能统⼀,且使⽤的地⽅较多的功能,就可以考虑 AOP 来统⼀处理了

除了统⼀的⽤户登录判断之外,AOP 还可以实现:

  1. 统⼀⽇志记录
  2. 统⼀⽅法执⾏时间统计
  3. 统⼀的返回格式设置
  4. 统⼀的异常处理
  5. 事务的开启和提交等

也就是说使⽤ AOP 可以扩充多个对象的某个能⼒,所以 AOP 可以说是 OOP(Object Oriented Programming,⾯向对象编程)的补充和完善


三、AOP 的组成

  1. 切⾯(Aspect)
    切⾯(Aspect)由切点(Pointcut)和通知(Advice)组成,它既包含了横切逻辑的定义,也包括了连接点的定义

切⾯是包含了通知、切点和织入的类,相当于 AOP 实现的某个功能的集合。切面定义了 AOP是针对哪一个统一的功能,比如用户登录效验功能就是一个切面

  1. 连接点(Join Point)
    应⽤执⾏过程中能够插⼊切⾯的⼀个点,这个点可以是⽅法调⽤时,抛出异常时,甚⾄修改字段时。切⾯代码可以利⽤这些点插⼊到应⽤的正常流程之中,并添加新的⾏为

连接点是需要被增强某个 AOP 功能的所有⽅法,所有满足拦截规则的方法就称之为连接点

  1. 切点(Pointcut)
    Pointcut 的作⽤就是定义⼀组 AOP 拦截规则(使⽤ AspectJ pointcut expression language 来描述)来匹配 Join Point,给满⾜规则的 Join Point 添加 Advice

  2. 通知(Advice)
    1.切⾯也是有⽬标的 ,也有必须完成的⼯作。在 AOP 术语中,切⾯的⼯作被称之为通知 ,即具体要实现的 AOP 功能!
    2.通知:定义了切⾯是什么,何时使⽤,其描述了切⾯要完成的⼯作,还解决何时执⾏这个⼯作的问题

Spring 切⾯类中,可以在⽅法上使⽤以下注解,会设置⽅法为通知⽅法,在满⾜条件后会通知本
⽅法进⾏调⽤:

  1. 前置通知使⽤ @Before:通知⽅法会在⽬标⽅法调⽤之前执⾏
  2. 后置通知使⽤ @After:通知⽅法会在⽬标⽅法返回或者抛出异常后调⽤
  3. 返回之后通知使⽤ @AfterReturning:通知⽅法会在⽬标⽅法返回后调⽤
  4. 抛异常后通知使⽤ @AfterThrowing:通知⽅法会在⽬标⽅法抛出异常后调⽤
  5. 环绕通知使⽤ @Around:通知包裹了被通知的⽅法,在被通知的⽅法通知之前和调⽤之后执⾏⾃定义的⾏为

后面我们会讲解如何使用 !!


四、Spring AOP 的实现

接下来我们使⽤ Spring AOP 来实现⼀下 AOP 的功能,完成的⽬标是拦截所有 UserController ⾥⾯的⽅法,每次调⽤ UserController 中任意⼀个⽅法时,都要执⾏相应的通知事件,即为该方法增强某个 AOP 功能 !!

Spring AOP 的实现步骤如下:

  1. 添加 Spring AOP 框架⽀持
  2. 定义切⾯和切点
  3. 定义通知

添加 Spring AOP 框架⽀持:

在这里插入图片描述

在 Maven 仓库中,找到 AOP 框架依赖并在 pom.xml 中手动引入:

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

定义切⾯和切点:

切面指的是具体要处理的某⼀类问题,⽐如⽤户登录权限验证就是⼀个具体的问题,记录所有⽅法的执⾏⽇志就是⼀个具体的问题,切面定义的是某⼀类问题

在这里插入图片描述

代码实现:

@Aspect // 定义一个切面,表明此类为一个切面
@Component
public class UserAspect {
    //切点: 定义切点(设置拦截规则),这⾥使⽤ AspectJ 表达式语法
    @Pointcut("execution(* com.example.demo.controller.UserController.* (..))")
    public void pointcut() {
    }

注意:

  1. 在类上添加注解@Aspect ,表明此类为一个切面
  2. 其中 pointcut ⽅法为空⽅法,它不需要有⽅法体,此⽅法名就是起到⼀个“标识”的作⽤,标识下⾯的通知⽅法具体指的是哪个切点(因为切点可能有很多个)
  3. 定义切点(设置拦截规则),这⾥使⽤ AspectJ 表达式语法 !上述表示的是当调用 controller.UserController 中的所有方法时,就会触发拦截规则

切点表达式说明(AspectJ 表达式语法):

AspectJ ⽀持三种通配符

在这里插入图片描述

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

execution(<修饰符><返回类型><包.类.⽅法(参数)><异常>) //修饰符和异常可以省略

如下对应关系:

在这里插入图片描述

表达式示例:

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


定义通知(补充前面各通知示例):

    //前置通知
    @Before("pointcut()")
    public void doBefore() {
        System.out.println("前置通知:被执行了 !");
    }

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

    // return 之后通知
    @AfterReturning("pointcut()")
    public void doAfterReturning() {
        System.out.println("执⾏ AfterReturning ⽅法");
    }

    // 抛出异常之后通知
    @AfterThrowing("pointcut()")
    public void doAfterThrowing() {
        System.out.println("执⾏ doAfterThrowing ⽅法");
    }

    // 添加环绕通知:将整个目标方法包裹起来了
    @Around("pointcut()")
    public Object doAround(ProceedingJoinPoint joinPoint) {
        Object obj = null;
        System.out.println("Around ⽅法开始执⾏");
        try {
            // 执⾏拦截⽅法,以及拦截方法所对应的相应通知
            obj = joinPoint.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        System.out.println("Around ⽅法结束执⾏");
        return obj;
    }

Controller 中的实现:

@RestController
@RequestMapping("/user")
public class UserController {

    @RequestMapping("/sayhi")
    public String sayhi(){
        System.out.println("执行了 目标方法 !");
        return "HI WORLD !";
    }
}

url 请求验证并观察控制台结果:

在这里插入图片描述

分析可知,环绕通知将整个目标方法包裹起来了,所以最先和最后执行的都是环绕通知。其次在目标方法执行之前执行了前置通知,目标方法返回后执行了返回通知,返回结束后才执行的后置通知!而目标代码中没有抛出异常,所以并没有执行抛出异常后通知 !!

我们制造一个异常,来观察 抛出异常后通知

修改controller代码,增加除数异常:

@RestController
@RequestMapping("/user")
public class UserController {

    @RequestMapping("/sayhi")
    public String sayhi(){
        System.out.println("执行了 目标方法 !");
        int num = 10/0;
        return "HI WORLD !";
    }
}

观察控制台结果:

在这里插入图片描述

当出现异常后,才会执行上述异常通知 !!


五、Spring AOP 实现原理

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

在这里插入图片描述

Spring AOP ⽀持 JDK Proxy 和 CGLIB ⽅式实现动态代理。默认情况下,实现了接⼝的类,使⽤AOP 会基于 JDK ⽣成代理类,没有实现接⼝的类,会基于 CGLIB ⽣成代理类

在这里插入图片描述

1、织⼊(Weaving):代理的⽣成时机

织⼊是把切⾯应⽤到⽬标对象并创建新的代理对象的过程

在⽬标对象的⽣命周期⾥有多个点可以进⾏织⼊:

  1. 编译期:切⾯在⽬标类编译时被织⼊。这种⽅式需要特殊的编译器。AspectJ的织⼊编译器就
    是以这种⽅式织⼊切⾯的。
  2. 类加载器:切⾯在⽬标类加载到JVM时被织⼊。这种⽅式需要特殊的类加载器
    (ClassLoader),它可以在⽬标类被引⼊应⽤之前增强该⽬标类的字节码。AspectJ5的加载
    时织⼊(load-time weaving. LTW)就⽀持以这种⽅式织⼊切⾯。
  3. 运⾏期:切⾯在应⽤运⾏的某⼀时刻被织⼊。⼀般情况下,在织⼊切⾯时,AOP容器会为目标对象动态创建⼀个代理对象。SpringAOP就是以这种⽅式织⼊切⾯的

2、动态代理

此种实现在设计模式上称为动态代理模式,在实现的技术⼿段上,都是在 class 代码运⾏期,动
态的织⼊字节码

我们学习 Spring 框架中的AOP,主要基于两种⽅式:JDK 及 CGLIB 的⽅式。这两种⽅式的代理
⽬标都是被代理类中的⽅法,在运⾏期,动态的织⼊字节码⽣成代理类

  1. CGLIB是Java中的动态代理框架,主要作⽤就是根据⽬标类和⽅法,动态⽣成代理类。
  2. Java中的动态代理框架,⼏乎都是依赖字节码框架(如 ASM,Javassist 等)实现的。
  3. 字节码框架是直接操作 class 字节码的框架。可以加载已有的class字节码⽂件信息,修改部
    分信息,或动态⽣成⼀个 class

2.1、JDK 动态代理实现

JDK 实现时,先通过实现 InvocationHandler 接⼝创建⽅法调⽤处理器,再通过 Proxy 来创建代
理类,以下为代码实现:

//动态代理:使⽤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();
    }
}

2.2、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,
                            MethodProxy methodProxy) 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();
    }
}

2.3、JDK 和 CGLIB 实现的区别

  1. JDK 实现,要求被代理类必须实现接⼝,之后是通过 InvocationHandler 及 Proxy,在运⾏时
    动态的在内存中⽣成了代理类对象,该代理对象是通过实现同样的接⼝实现(类似静态代理
    接⼝实现的⽅式),只是该代理类是在运⾏期时,动态的织⼊统⼀业务逻辑字节码来完成。
  2. CGLIB 实现,被代理类可以不实现接⼝,是通过继承被代理类,在运⾏时动态的⽣成代理类
    对象。默认情况下 Spring AOP 都会采用CGLIB 来实现动态代理,但是由于它是通过继承被代理类来实现的,所以它不能代理最终类(被 final修饰的类,不能被继承)

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

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

相关文章

【Linux常见指令以及权限理解】基本指令(2)

写在前面 今天我们继续学习Linux的基本指令&#xff0c; 这里是上一篇博客的链接&#xff1a;http://t.csdn.cn/9AgHP 接下来我会继续讲解Linux指令相关内容。 目录 写在前面 1. man 描述&#xff1a; 用法&#xff1a; 例子&#xff1a; 例1&#xff1a; 例2&#…

MC9S12G128开发板—实现按键发送CAN报文指示小车移动功能

实验环境&#xff1a;MC9S12G128开发板 基本功能&#xff1a;控制开发板上的按键&#xff0c;模拟车辆移动的上下左右四个方位&#xff0c;通过can通信告诉上位机界面&#xff0c;车辆轨迹的移动方位。 1. 1939报文发送的示例代码 MC9S12G128开发板1939协议发送can报文数据的…

redmine问题跟踪系统4.1版本一键安装包下载

很好用的项目管理&#xff0c;缺陷跟踪系统&#xff0c;开源免费使用 Version 4.1.1-4 2020-08-31 由 redmineplugins.cn Admin 在 超过 2 年 之前添加 Version 4.1.1-4 2020-08-31 Maintenance releaseUpdated Apache to 2.4.46Updated Git to 2.28.0Updated PHP to 7.3.21U…

初识uniapp

创建小程序 依次点击HBuilderx 左上方的按钮&#xff1a;文件->新建->项目 然后打开该界面&#xff0c;输入项目名称&#xff0c;点击 浏览 按钮&#xff0c;可以选择项目保存的目录&#xff0c;这些完成后点击 创建 按钮就好了 比如小颖的项目名叫 &#xff1a;test-y…

基于一致性的半监督学习用于诊断x线片分类

文章目录 Consistency-Based Semi-supervised Evidential Active Learning for Diagnostic Radiograph Classification摘要方法Evidential-based Semi-supervised LearningEvidential-based Active Learning Consistency-Based Semi-supervised Evidential Active Learning for…

Java+Angular开发的医院信息管理系统源码,系统部署于云端,支持多租户

云HIS系统源码&#xff0c;采用云端SaaS服务的方式提供 基于云计算技术的B/S架构的云HIS系统源码&#xff0c;采用云端SaaS服务的方式提供&#xff0c;使用用户通过浏览器即能访问&#xff0c;无需关注系统的部署、维护、升级等问题&#xff0c;系统充分考虑了模板化、配置化、…

第四届CECC中国计算机教育大会召开,飞桨持续加码产教融合教育新生态

‍‍大模型作为人工智能发展的新方向&#xff0c;对人才的需求和培养带来了新挑战。4月21日至22日&#xff0c;以“新时代 新挑战 新任务”为主题的第四届中国计算机教育大会&#xff08;CECC&#xff09;在厦门召开&#xff0c;飞桨承办“人工智能与大模型”论坛同期举办。立足…

【Python安卓开发】BeeWare框架:环境准备

&#x1f4ad; 写在前面&#xff1a;我们假定读者已经安装好了 3.8 版本后的 Python&#xff0c;并且安装好了依赖项&#xff0c;在 Windows 上构建 BeeWare 应用程序需要 Git&#xff0c;你可以可以从 git-scm.org 网站下载。安装完毕后重新启动 cmd&#xff0c;然后就可以准备…

Compiler- 循环展开

循环展开不仅在编译原理中有涉及到&#xff0c;笔者记得在CSAPP里面也提到了这种优化方法。 话不多说&#xff0c;我们先来看个例子。 int loop(int a) {int result 0;for(int i 0; i < a; i){result i;}return result; }int loop1(int a) {int result 0;int len a/2…

虚拟化、容器与Docker基本介绍以及安装部署镜像加速

目录 一.虚拟化概述 1.虚拟化是什么&#xff1f; 2.虚拟化两大组件 3.虚拟化类型 4.虚拟化功能 二.容器概述 1.容器是什么&#xff1f; 2.容器的优点 3.容器的缺点 三.Docker概述 1.Docker是什么&#xff1f; 2.Docker容器与虚拟机的区别 3.容器在内核中支持两种重…

从0开始学习docker-1.mysql安装

从0开始学习docker 环境安装安装mysql备份镜像删除镜像镜像恢复 环境安装 yum update yum install -y yum-utils device-mapper-persistent-data lvm2 yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo yum install docker-ce systemc…

IOS工程:NSThread sleepForTimeInterval的使用,游戏中途中断(接电话或者点击Home按钮),重新回到游戏音频音效失效问题

IOS工程&#xff1a;NSThread sleepForTimeInterval的使用&#xff0c;游戏中途中断&#xff08;接电话或者点击Home按钮&#xff09;&#xff0c;重新回到游戏音频音效失效问题 设备/引擎&#xff1a;Mac&#xff08;11.7&#xff09;/cocos 开发工具&#xff1a;Xcode 开发…

composer 安装gitlab私有库

开发PHP项目&#xff0c;免不了用composer。最近做一个项目&#xff0c;需要到公司内部开发的核心包&#xff0c;核心包放在内网搭建的gitlab仓库中&#xff0c;于是我用composer进行下载&#xff0c;报错&#xff1a; Cloning into bare repository C:/Users/Administrator/A…

Mac电脑系统管家CleanMyMac X4.13安装下载使用教程

当我们刚刚拿到那闪亮的新Mac时&#xff0c;是多么令人愉悦的一种感觉&#xff01;随着时间的推移&#xff0c;你可能已经注意到它的速度减慢&#xff0c;磁盘空间逐渐减少。不用担心&#xff0c;CleanMyMac会为你的电脑带来焕然一新的体验。这篇文章将向你介绍CleanMyMac的奇妙…

企业oa管理系统是什么

办公自动化&#xff08;Office Automation&#xff0c;简称OA&#xff09;&#xff0c;是将计算机、通信等现代化技术运用到传统办公方式&#xff0c;进而形成的一种新型办公方式。 办公自动化利用现代化设备和信息化技术&#xff0c;代替办公人员传统的部分手动或重复性业务活…

史上最全Python14张思维导图+字节跳动出品《Python背记手册》,高清PDF限时开放!

前言 Python是一种语法简单、功能强大的编程语言&#xff0c;它既适用于传统编程语言擅长的Web开发、移动开发、游戏开发、桌面应用&#xff0c;又适用于当前流行的人工智能、大数据、科学计算、金融分析…… 如果你想要学习一门编程语言Python肯定是一个不错的选择&#xff…

Scala之集合(2)

目录 集合基本函数&#xff1a; &#xff08;1&#xff09;获取集合长度 &#xff08;2&#xff09;获取集合大小 &#xff08;3&#xff09;循环遍历 &#xff08;4&#xff09;迭代器 &#xff08;5&#xff09;生成字符串 &#xff08;6&#xff09;是否包含 衍生集合…

itop-3568 开发板系统编程学习笔记(20)看门狗应用编程

【北京迅为】嵌入式学习之Linux系统编程篇 https://www.bilibili.com/video/BV1zV411e7Cy/ 个人学习笔记 文章目录 看门狗简介看门狗编程命令&#xff08;方法&#xff09;开启和关闭看门狗设置超时时间获取超时时间喂狗 看门狗底层简析看门狗编程实验 看门狗简介 看门狗&#…

MiniGPT-4开源了:看图聊天、教学、创作、搭网站

深度学习系列文章 文章目录 深度学习系列文章前言MiniGPT4效果展示 前言 一个月前&#xff0c;OpenAI 总裁 Greg Brockman 向世人展示了 GPT-4 令人惊讶的多模态能力&#xff0c;如从手写文本直接生成网站和识别图像中的幽默元素等。 尽管目前 OpenAI 暂未对 GPT-4 用户开放这…

农业灌溉以电折水测控终端-开启用水计量新模式

产品概述 农业灌溉以电折水测控终端&#xff08;MGTR-W&#xff09;是一款拥有“最强大脑”的农业水资源计量管理终端&#xff0c;内置以电折水逻辑运算&#xff0c;主要研究耗电量与取水量之间的关系&#xff0c;分析水电折算系数&#xff0c;进而通过计算耗电量与水电折算系数…