Spring AOP 面向切面编程

news2024/11/14 11:02:36

1.AOP是什么

我们之前学过 OOP 面向对象编程, 那么 AOP 是否就比 OOP 更牛逼呢? 是否也是面向过程到面向对象这种质的跨越呢? 其实不是, 它和 OOP 思想是一种互补的关系, 是对 OOP 思想的一种补充.

AOP (Aspect Oriented Programming) : 面向切面编程, 它是一种思想, 它是对某一类事情的集中处理.

那什么叫面向切面编程呢 ? 这个切面是数学中的切面吗 ? 其实不是, 这都是英译汉的锅.

就拿常见博客中的用户登录功能来说, 我们在写博客的时候, 需要验证用户登录; 我们在发布博客的时候, 也需要验证用户登录; 我们在查看文章列表的时候,也需要验证用户登录. 很多地方都需要做这个事情, 那么我就可以将这个事提出来, 做成统一的处理. 所以验证用户登录这个功能就可以使用 AOP来处理.

听到这, 大概就明白了, AOP 其实是不能代替 OOP 的, 只有集中或者可以高度抽象的一类事情才可以使用 AOP 思想来处理, 而并不是所有事情都是集中, 可以高度抽象的, 就比如注册功能, 登录功能, 我只是在注册, 登录的时候才做这件事情.

1.1 AOP 思想的好处

我们分析 "面向过程 -> 封装 -> AOP" 这三种操作慢慢升级的过程, 就能从中体会这种看似不起眼的升级. 这也是日常开发和企业级开发的区别所在.

1. 开发初级阶段 (面向过程) : 所有的方法都自己实现一遍. 分别在修改, 删除, 发布博客三个业务中自己写验证用户登录的功能

2. 开发中级阶段 (封装) : 封装成公共的方法, 在需要的地方进行调用. 将验证用户登录功能提取出来, 写成一个公共的方法, 分别在修改, 删除, 发布博客中调用该方法. (降低代码耦合)

3. 开发的高级阶段 (AOP) : 使用 AOP (拦截器/过滤器) 对某个功能做统一的处理. 使用 AOP 思想对验证登录这种可以高度抽象的业务进行集中处理, 不再需要在每个方法中进行调用. (再次降低代码耦合)

【举个例子加以理解】

假如我们是深圳高铁站的高管,这里有四个站台,分别开往广州、上海、杭州、北京四个地方, 当乘客们进站前, 我们需要检查乘客是否携带易燃易爆物品.

初级阶段, 我们分别在四个站台安排自己的员工进行安全检查.
中级阶段, 我们将检查工作外包给保安公司, 让保安人员分别在四个站台进行安全检查.
高级阶段, 我们在进站口安排保安人员进行安全检查, 不需要在各个站台进行安全检查.

这就是 AOP 思想的好处. 当然不使用 AOP也能做事情, 但是不够优雅, 不够高级.如果你封装的代码, 你有一百个, 一千个地方需要调用, 某一天, 你接口的参数变了, 那么你这一百个, 一千个调用的地方都要改, 代码耦合度就相当高了. 这中看似不起眼的升级, 在代码没有发生变动的时候, 大家都能正常开发, 正常使用. 一旦代码发生变动, 两种方式的差别就体现出来了.

除了统一的用户登录效验之外, AOP 还可以实现如下功能 (后面会用代码实现)

  • 统一的日志记录

  • 统一方法执行时间统计

  • 统一的返回格式设置

  • 统一的异常处理

  • 事务的开启和提交等

1.2 Spring AOP 是什么

既然我们知道 AOP 是什么, 又知道了 AOP 可以用来干嘛, 以及它的好处, 那么什么又是 Spring AOP 呢

AOP 是一种思想, 而 Spring AOP 是它的具体实现. 就类似 Spring IoC 和DI 的关系一样.

2.Spring AOP 应该如何学习

Spring AOP 就学三样东西:

1. AOP 的组成. 【概念】
2. Spring AOP 的实现.
3. Spring AOP 的实现原理.

2.1 AOP 的组成

1. 切面 (Aspect) : 定义 AOP 业务类型的. (当前业务类型是用户登录效验, 还是统一异常处理等等)
2. 连接点 (Join Point) : 有可能调用 AOP 的地方就叫做一个连接点. (前面例子在中级阶段的各个业务中调用公共方法的地方, 就是一个一个的连接点.)
3. 切点 (Pointcut) : 定义 AOP 拦截规则.
4. 通知 (Advice) : 定义什么时机, 做什么事. [增强方法]

通知分为五类:

  • 前置通知 : 拦截的目标方法之前执行的通知 (事件).

  • 后置通知 : 拦截的目标方法之后执行的通知 (事件).

  • 返回之后通知 : 拦截的目标方法返回数据之后通知.

  • 抛出异常之后的通知 : 拦截的目标方法抛出异常之后执行的通知.

  • 环绕通知 : 在拦截方法执行前后都执行的通知.

2.2 Spring AOP 的实现

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

<dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
  1. 定义切面和切点

就拿用户登录效验功能来举例>>

@Component
@Aspect // 标识当前类为一个切面
public class LoginAop {

    // 定义切点 (拦截的规则, 此处使用的是 AspectJ 表达式语法)
    @Pointcut("execution(* com.example.demo.controller.UserController.*(..))")
    // 该方法没啥作用, 只是后续需要使用这个方法名
    public void pointcut() {
    }
}
  1. 定义通知

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

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

    // return 之前通知
    @AfterReturning("pointcut()")
    public void doAfterReturning() {
        System.out.println("执行了返回之后通知");
    }

    // 抛出异常之前通知
    @AfterThrowing("pointcut()")
    public void doAfterThrowing() {
        System.out.println("执行了返回之后通知");
    }

    // 环绕通知
    @Around("pointcut()")
    public Object doAround(ProceedingJoinPoint joinPoint) {  // 拿到目标方法的执行对象
        // 这个对象是框架能否继续执行后续流程的对象, 与目标方法是否返回值, 以及返回类型无关
        Object res = null;
        // 前置业务代码
        System.out.println("执行了环绕通知的前置方法");
        try {
            // 执行目标方法
            res = joinPoint.proceed();
        } catch (Throwable e) {
            e.printStackTrace();
        }
        // 后置业务代码
        System.out.println("执行了环绕通知的后置方法");
        return res;
    }

切点,通知都在 LoginAop 这个类中.

【分析】

1. 切点的拦截规则表示,拦截 UserController 类里面的所有方法. (后面细讲拦截规则)
2. 切点里的 pointcut() 方法是为了给后面的通知使用该方法名.
3. 前四种通知都很简单, 除了注解不一样, 其他都一样. 主要是环绕通知, 它带有参数, 参数 joinPoint 的意义就是拿到目标方法中的执行对象, 也就是 UserController 中的所有方法的执行对象. 用这个对象调用 proceed() 就是执行 UserController 中的所有方法. 环绕通知的返回值 res , 和目标方法的方法类型无关, 它只决定框架能否继续执行后续流程.

【测试】

现在我们测试一下我们的 AOP 是否可以拦截 UserController 中的方法.

@RestController
@RequestMapping("/user")
@Slf4j
public class UserController {
    @Autowired
    private UserService userService;

    /**
     * 查询所有用户
     * @return
     */
    @RequestMapping("/select")
    public List<UserInfo> selectAll() {
        log.debug("执行了 selectAll 方法");
        return userService.selectAll();
    }
}

当我们通过流浪器访问 selectAll() 方法时, 观察控制台的信息:

【结论】

1. 成功拦截了 UserController 中的方法.
2. 环绕通知的前置方法在最前面执行, 环绕通知的后置方法在最后执行.
3. 通过上面 AOP的简单实现, 我们大概也就知道了如何对 "用户登录效验功能进行统一处理了. 只需要在同一将所有调用用户登录校验的方法写在一个类中, 或者一个文件夹下, 然后设置对应的拦截规则即可.

如果想进一步验证 "拦截规则" 是否正确, 可以在 controller 包下再建一个 TestController 类, 然后写一个方法, 并通过浏览器访问, 观察是否还打印了这几个通知方法. (答案肯定是没有, 下来可以自己试一下)

🍁切点表达式的说明

AspectJ 支持三种通配符:

* : 匹配任意字符, 只匹配一个元素 (包,类,或⽅法,⽅法参数)
.. : 匹配任意字符, 可以匹配多个元素, 在表示类时, 必须和 * 联合使用.
+ : 表示按照类型匹配指定类的所有类, 必须跟在类名后面, 如 com.hl.Cat+, 表示继承该类的所有子类, 包括本身.

切点表达式由切点函数组成, 最常用的切点函数就是 execution() , 用来匹配方法, 语法为:

execution(<修饰符><返回类型><包.类.⽅法(参数)><异常>)

【具体规则】>>>

修饰符(一般省略)

  • public 公共方法

  • * 任意

返回类型(不能省略)

  • void 没有返回值

  • String 返回字符串类型

  • * 任意

  • com.example.demo 固定包

  • com.example.demo.*.controller demo 下面任意子包 (com.example.demo.aop.controller)

  • com.example.demo.. demo 包下面所有子包 (含自己)

  • com.example.demo.*.controller.. demo 包下面任意子包 / 固定目录 controller / controller 目录任意包

  • UserController 指定类

  • *service 以 service 结尾的类

  • User* 以 User 开头的类

  • * 任意

方法名

  • addUser 固定方法

  • add* 以 add 开头 方法

  • *Add 以 Add 结尾的方法

  • * 任意

参数

  • () 无参

  • (int) 一个整型

  • (int, int) 两个整型

  • (..) 任意参数

throws (可省略, 一般不写)

2.3 Spring AOP 的实现原理

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

什么是动态代理 >>>

程序运行期间产生的代理, 就叫做动态代理.

举个例子: 就拿放鞭炮这件事来说吧.
当政府还没有批准放鞭炮的时候, 你就是一个卖鞭炮的商贩了, 此时你就是一个静态代理.
如果说你是因为政府批准放鞭炮后, 你在一个广场上看见很多人都聚集在这里放鞭炮, 然后你觉得有利可图, 于是你就进购很多鞭炮, 你就将鞭炮拿到广场上去卖. 像这种本身不是烟花商贩, 只是看见有利可图才做起了流动商贩, 这就叫做动态代理.

动态代理也是一种思想, JDK Proxy CGLIB 这两种方式就是动态代理的具体实现.

  • 如果是一个实现接口的类, 使用 AOP 会基于 JDK Proxy 生成代理类.

  • 如果是一个没有实现接口的类, 使用 AOP 会基于 CGLIB 生成代理类.

织入(Weaving) : 代理的生成时机

织入是把切⾯应用到目标对象并创建新的代理对象的过程,切面在指定的连接点被织入到目标对
象中.

在目标对象的声明周期里, 有三个时期可以进行织入 (类的三个阶段):

  • 编译期: 切面在目标类编译时被织入. 这种方式需要特殊的编译器.

  • 类加载期: 切面在目标类加载到JVM时被织入.这种方式需要特殊的类加载器,它可以在目标类被引入应用之前增强该目标类的字节码.

  • 运行期: 切面在应用运行的某一时刻被织入.

我们学习 Spring AOP , 主要就是基于 JDK Proxy 和 CGLIB 这两种方式. 这两种方式的代理目标都是在被代理类中的方法, 在运行期间, 动态的织入字节码生成代理类. 这种实现在设计模式上称为动态代理模式.

【常见面试题】

JDK Proxy 和 CGLIB 的区别 >>

1. JDK Proxy 的实现,要求 被代理类必须实现接口,然后通过 InvocationHandler 及 Proxy,在运行期间动态的在内存中生成了代理类对象.
2. CGLIB 实现, 被代理类可以不实现接口,是通过继承被代理类,在运行时动态的生成代理类
对象.

对于 JDK Proxy 和 CGLIB 的实现原理和区别建议可以去简单看看源码, 这对后续的 Spring AOP 的统一功能处理的理解以及实现都是有很大的帮助.


本篇文章就到这里了, 谢谢观看!!

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

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

相关文章

Arrays数组

1.Arrays.toString()方法:输出数组内容 2.Arrays.sort()方法:给数组排序&#xff0c;默认升序 对其他对象数组进行排序 一个对象数组&#xff0c;排序算法需要重复比较数组中的元素。不同的类比较元素的规则是不同的&#xff0c;但是排序算法只应该调用类提供的比较方法&#…

netty中channelHandler实现原理及最佳实践|极客星球

为持续夯实MobTech袤博科技的数智技术创新能力和技术布道能力&#xff0c;本期极客星球邀请了企业服务研发部工程师梁立从 TCP 的粘包/半包、 Netty 处理粘包/半包及源码分析、 开源项目对 channelHandler最佳实践三方面对《netty 中channelHandler的原理与最佳实践》进行了全面…

【Ctfer训练计划】——(九)

作者名&#xff1a;Demo不是emo 主页面链接&#xff1a;主页传送门创作初心&#xff1a;舞台再大&#xff0c;你不上台&#xff0c;永远是观众&#xff0c;没人会关心你努不努力&#xff0c;摔的痛不痛&#xff0c;他们只会看你最后站在什么位置&#xff0c;然后羡慕或鄙夷座右…

Python+Selenium4元素定位_web自动化(3)

目录 0. 上节回顾 1. 八大定位 2. 定位器 3. CSS选择器 4. XPath选择器 4.1. XPath语法 4.2. XPath 函数 5. 相对定位 5.1 XPath 中的相对定位【重点】 5.1.1 相对路径 5.1.2 轴 5.2 selenium4 中的相对定位 总结 0. 上节回顾 浏览器的一般操作 浏览器的高级操作…

【sciter】:JSX 组件实现数据持久化

# 原理 组件数据持久化指的是:重新加载组件后,能否将重新加载前组件所存在的数据,在重新加载后数据依旧保存在组件中。 组件数据持久化实现原理:将每次更新组件数据同步到 Storage 中。并且监听组件重新加载(刷新),在刷新前将 Storage 关闭(确保数据不丢失)。当加载…

idea中添加git使用时文件不同颜色,标签不同颜色,代码不同颜色代表的含义

文章目录文件的颜色标签的颜色合并代码时不同颜色区块的含义文件的颜色 绿色——已经加入控制暂未提交&#xff1b; 红色——未加入版本控制&#xff1b;自己建立新文件后就是红色的&#xff0c;出现红色的一定要Add到git中&#xff0c;不然不能上传到远程仓库 蓝色——加入&am…

关于markdown相关语法的学习

众所周知&#xff0c;一个好的项目需要搭配一个好的项目说明&#xff0c;就行吃饺子需要蘸醋一样&#xff0c;没有醋的饺子&#xff0c;你仅仅吃掉了他的肉体&#xff0c;而得不到他的灵魂。下面开始吃饺子&#xff0c;不对&#xff0c;是开始学习markdown文件的基础语法&#…

在采购管理过程中使用技术有什么好处?

采购过程不总是简单直接的&#xff0c;人工采购过程非常耗费人力和时间&#xff0c;并且涉及大量文书工作。另一方面&#xff0c;当你在采购过程中使用技术时&#xff0c;比如使用SRM采购管理系统&#xff0c;会节省很多时间&#xff0c;使整个过程变得更加简单和轻松。 在讨…

Homekit智能家居创意DIY之智能吸顶灯

买灯要看什么因素 好灯具的灯光可以说是家居的“魔术师”&#xff0c;除了实用的照明功能外&#xff0c;对细节的把控也非常到位。那么该如何选到一款各方面合适的灯呢&#xff1f; 照度 可以简单理解为清晰度&#xff0c;复杂点套公式来说照度光通量&#xff08;亮度&#…

【达梦8】vm 虚拟机centos 7 安装达梦8 数据库

目录准备下载安装包版本选择安装前准备【登录root用户】创建用户【登录root用户】设置限制资源配置【登录dmdba用户】上传iso文件挂载iso创建安装路径开始安装【登录dmdba用户】安装【登录dmdba用户】初始化实例初始化注意事项开始初始化启动数据库启动方式1 &#xff08;推荐&…

Linux oom机制

Linux oom机制前言1 内存回收2 OOM基本原理2.1 虚拟内存OOM2.2 物理内存OOM3 oom配置参数3.1 panic_on_oom3.2 oom_kill_allocating_task3.3 oom_dump_tasks4 安卓LMK简介5 总结前言 Linux oom是由于内存泄漏或者内存使用不合理而导致的问题。 在讲OOM之前&#xff0c;我们先…

数据库,计算机网络、操作系统刷题笔记25

数据库&#xff0c;计算机网络、操作系统刷题笔记25 2022找工作是学历、能力和运气的超强结合体&#xff0c;遇到寒冬&#xff0c;大厂不招人&#xff0c;可能很多算法学生都得去找开发&#xff0c;测开 测开的话&#xff0c;你就得学数据库&#xff0c;sql&#xff0c;oracle…

Java实现栈结构

目录 一、栈概述 二、模拟实现栈 1、入栈 2、出栈 3、取栈顶元素 三、栈的应用 1、逆序打印链表 2、括号匹配问题 3、逆波兰表达式求值 4、栈的压入、弹出序列 5、最小栈 一、栈概述 栈&#xff08;Stack&#xff09;也是数据结构的一种&#xff0c;属于线性数…

Usaco Training刷怪旅 第三层 第五题:Wormholes

美国人出的题真的难&#xff08;个人感觉&#xff09;&#xff0c;看起来还行&#xff0c;做起来就是另外一会儿事儿了 Farmer Johns hobby of conducting high-energy physics experiments on weekends has backfired, causing N wormholes (2 < N < 12, N even) to ma…

基于物体基元空间几何特征的移动激光雷达点云街道树提取与分割

paper题目&#xff1a;Street Tree Extraction and Segmentation from Mobile LiDAR Point Clouds Based on Spatial Geometric Features of Object Primitives Abstract 从移动光探测与测距(LiDAR)点云中提取行道树仍然面临挑战&#xff0c;如在复杂的城市环境中提取精度低、…

浅谈哨兵机制的原理

文章目录哨兵机制的基本流程监控:主观下线&#xff1a;客观下线&#xff1a;选主&#xff1a;筛选&#xff1a;打分&#xff1a;通知&#xff1a;哨兵机制的基本流程 哨兵其实就是一个运行在特殊模式下的 Redis 进程&#xff0c;主从库实例运行的同时&#xff0c;它也在运行。…

el-input中放入elbutton

如图&#xff0c;如何在element组建的el-input的后缀放一个可点击的按钮或者标签 <el-input><el-button style"padding-right:10px" slot"suffix" type"text" >选择</el-button></el-input>在el-input的官网介绍中&…

jina实现并发扩展的调研

基于之前对于clip-as-service的调研&#xff0c;我在官方文档中看到横向扩展页面中的副本相关内容&#xff0c;可以解决并发问题&#xff0c;于是动手验证了一番 参考链接 &#xff08;官方文档&#xff09;link 官方文档的描述 首先我整了一个服务端 如果需要开启副本&…

线段相交判断

一、问题描述已知两条线段P1P2和Q1Q2&#xff0c;判断P1P2和Q1Q2是否相交&#xff0c;若相交&#xff0c;求出交点。两条线段的位置关系可以分为三类&#xff1a;[1] 有重合部分;[2] 无重合部分但有交点;[3] 无交点。注意&#xff1a;这里讨论的是两条线段是否相交&#xff0c;…

典型相关分析(附SPSS操作)

典型相关分析&#xff1a;研究两组变量&#xff08;每个变量中都可能有多个指标&#xff09;之间相关关系的一种多元统计方法。他能够揭示出两组变量之间的内在联系。选能较为综合、全面的衡量所在组的内在规律。一组变量最简单的综合形式就是该组变量的线性组合。典型相关分析…