Spring之浅谈AOP技术

news2024/11/13 9:22:05

前言

AOP:Aspect Oriented Programming(面向切面编程),一种编程范式,指导开发者如何组织程序结构。

        OOP(Object Oriented Programming)面向对象编程

AOP和OOP一样都是一种编程思想,而编程思想的主要内容便是指导程序员如何编写程序,所以它们两个是不同的编程范式

1.AOP的作用

作用:在不惊动原始设计的基础上为其进行功能增强,类似于代理模式技术;这也是Spring的理念,对代码做到无侵入式

2.AOP核心概念

首先先看一个案例,代码如下:

@Repository
public class BookDaoImpl implements BookDao {
    public void save() {
        //记录程序当前执行执行(开始时间)
        Long startTime = System.currentTimeMillis();
        //业务执行万次
        for (int i = 0;i<10000;i++) {
            System.out.println("book dao save ...");
        }
        //记录程序当前执行时间(结束时间)
        Long endTime = System.currentTimeMillis();
        //计算时间差
        Long totalTime = endTime-startTime;
        //输出信息
        System.out.println("执行万次消耗时间:" + totalTime + "ms");
    }
    public void update(){
        System.out.println("book dao update ...");
    }
    public void delete(){
        System.out.println("book dao delete ...");
    }
    public void select(){
        System.out.println("book dao select ...");
    }
}

那么上面的代码依次执行save(),delete(),update()和select()后,其控制台的各种打印结果如下图:

 那么奇怪的来了,计算万次消耗时间只在save()中有,为何delete()和update()也会有,那么select()为何又没有?

上述代码中就使用了Spring的AOP,实现了无侵入式的代码设计

Spring实现AOP

 1.Spring的AOP是对一个类的方法不进行任何修改的前提下实现增强。对于上面的案例将save、update、delete和select方法看做一个个“连接点”;

2.上述的update和delete方法中,没有关于万次消耗时间的语句,但在执行后却已经有此功能,也就是说update和delete方法已经被加强过了,那么对加强过的方法取名为“切入点”;

3.由于update和delete在执行后都实现了万次消耗时间的方法,所有一个方法存放共性的功能,将这个方法取名为“通知”;

4.“通知”就是要增强的内容,会有多个;切入点是需要被增强的方法,也会有多个;哪个切入点需要添加哪个通知,就需要提前将它们之间的关系描述清除,那么对于通知和切入点之间的关系描述称为“切面”;

5.通知是一个方法,所以存放通知方法的类叫做“通知类”;

小结:

  • 连接点(JoinPoint):程序执行过程中的任意位置,粒度为执行方法、抛出异常、设置变量等

    • 在SpringAOP中,理解为方法的执行

  • 切入点(Pointcut):匹配连接点的式子

    • 在SpringAOP中,一个切入点可以描述一个具体方法,也可也匹配多个方法

      • 一个具体的方法:如com.itheima.dao包下的BookDao接口中的无形参无返回值的save方法

      • 匹配多个方法:所有的save方法,所有的get开头的方法,所有以Dao结尾的接口中的任意方法,所有带有一个参数的方法

    • 连接点范围要比切入点范围大,是切入点的方法也一定是连接点,但是是连接点的方法就不一定要被增强,所以可能不是切入点。

  • 通知(Advice):在切入点处执行的操作,也就是共性功能

    • 在SpringAOP中,功能最终以方法的形式呈现

  • 通知类:定义通知的类

  • 切面(Aspect):描述通知与切入点的对应关系。

3.AOP工作流程

流程1:Spring容器启动

  • 容器启动就需要去加载bean,哪些类需要被加载呢?

  • 需要被增强的类,如:BookServiceImpl

  • 通知类,如:MyAdvice

  • 注意此时bean对象还没有创建成功

流程2:读取所有切面配置中的切入点

如上图中,但是由于ptx()并未使用,所以并不会被读取。

流程3:初始化bean

判定bean对应的类中的方法是否匹配到任意切入点

  • 注意第1步在容器启动的时候,bean对象还没有被创建成功。

  • 要被实例化bean对象的类中的方法和切入点进行匹配

 

  • 匹配失败,创建原始对象,如UserDao

    • 匹配失败说明不需要增强,直接调用原始对象的方法即可。

  • 匹配成功,创建原始对象(==目标对象==)的==代理==对象,如:BookDao

    • 匹配成功说明需要对其进行增强

    • 对哪个类做增强,这个类对应的对象就叫做目标对象

    • 因为要对目标对象进行功能增强,而采用的技术是动态代理,所以会为其创建一个代理对象

    • 最终运行的是代理对象的方法,在该方法中会对原始方法进行功能增强

流程4:获取bean执行方法

  • 获取的bean是原始对象时,调用方法并执行,完成操作

  • 获取的bean是代理对象时,根据代理对象的运行模式运行原始方法与增强的内容,完成操作

验证容器中是否为dialing对象

  • 如果目标对象中的方法会被增强,那么容器中将存入的是目标对象的代理对象

  • 如果目标对象中的方法不被增强,那么容器中将存入的是目标对象本身。

验证思路

1.要执行的方法,不被定义的切入点包含,即不要增强,打印当前类的getClass()方法

2.要执行的方法,被定义的切入点包含,即要增强,打印出当前类的getClass()方法

3.观察两次打印的结果

 步骤1:修改App类,获取类的类型

public class App {
    public static void main(String[] args) {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
        BookDao bookDao = ctx.getBean(BookDao.class);
        System.out.println(bookDao);
        System.out.println(bookDao.getClass());
    }
}

步骤2:修改MyAdvice类,不增强

因为定义的切入点中,被修改成update1,所以BookDao中的update方法在执行的时候,就不会被增强,

所以容器中的对象应该是目标对象本身。

@Component
@Aspect
public class MyAdvice {
    @Pointcut("execution(void com.itheima.dao.BookDao.update1())")
    private void pt(){}
    
    @Before("pt()")
    public void method(){
        System.out.println(System.currentTimeMillis());
    }
}

  

步骤3:修改MyAdvice类,增强

因为定义的切入点中,被修改成update,所以BookDao中的update方法在执行的时候,就会被增强,

所以容器中的对象应该是目标对象的代理对象

@Component
@Aspect
public class MyAdvice {
    @Pointcut("execution(void com.itheima.dao.BookDao.update())")
    private void pt(){}
    
    @Before("pt()")
    public void method(){
        System.out.println(System.currentTimeMillis());
    }
}

4.AOP核心概念

  • 目标对象(Target):原始功能去掉共性功能对应的类产生的对象,这种对象是无法直接完成最终工作的

  • 代理(Proxy):目标对象无法直接完成工作,需要对其进行功能回填,通过原始对象的代理对象实现

目标对象就是要增强的类[如:BookServiceImpl类]对应的对象,也叫原始对象,不能说它不能运行,只能说它在运行的过程中对于要增强的内容是缺失的。

SpringAOP是在不改变原有设计(代码)的前提下对其进行增强的,它的底层采用的是代理模式实现的,所以要对原始对象进行增强,就需要对原始对象创建代理对象,在代理对象中的方法把通知[如:MyAdvice中的method方法]内容加进去,就实现了增强,这就是我们所说的代理(Proxy)。

5.AOP通知类型

上述注解代表的含义就是将通知添加到切入点方法执行的“前面”;

那么除了这个注解,还有能加在其他地方的注解

5.1类型介绍

  • AOP通知描述了抽取的共性功能,根据共性功能抽取的位置不同,最终运行代码时要将其加入到合理的位置

提供了5种通知类型:

  • 前置通知

  • 后置通知

  • 环绕通知

  • 返回后通知

  • 抛出异常后通知

(1)前置通知,追加功能到方法执行前,类似于在代码1或者代码2添加内容

(2)后置通知,追加功能到方法执行后,不管方法执行的过程中有没有抛出异常都会执行,类似于在代码5添加内容

(3)返回后通知,追加功能到方法执行后,只有方法正常执行结束后才进行,类似于在代码3添加内容,如果方法执行抛出异常,返回后通知将不会被添加

(4)抛出异常后通知,追加功能到方法抛出异常后,只有方法执行出异常才进行,类似于在代码4添加内容,只有方法抛出异常后才会被添加

(5)环绕通知,环绕通知功能比较强大,它可以追加功能到方法执行的前后,这也是比较常用的方式,它可以实现其他四种通知类型的功能

5.2通知类型的使用

环境准备

添加BookDao和BookDaoImpl类

public interface BookDao {
    public void update();
    public int select();
}

@Repository
public class BookDaoImpl implements BookDao {
    public void update(){
        System.out.println("book dao update ...");
    }
    public int select() {
        System.out.println("book dao select is running ...");
        return 100;
    }
}

创建Spring的配置类

@Configuration
@ComponentScan("com.itheima")
@EnableAspectJAutoProxy
public class SpringConfig {
}

创建通知类

@Component
@Aspect
public class MyAdvice {
    @Pointcut("execution(void com.itheima.dao.BookDao.update())")
    private void pt(){}

    public void before() {
        System.out.println("before advice ...");
    }

    public void after() {
        System.out.println("after advice ...");
    }

    public void around(){
        System.out.println("around before advice ...");
        System.out.println("around after advice ...");
    }

    public void afterReturning() {
        System.out.println("afterReturning advice ...");
    }
    
    public void afterThrowing() {
        System.out.println("afterThrowing advice ...");
    }
}

编写App运行类

public class App {
    public static void main(String[] args) {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
        BookDao bookDao = ctx.getBean(BookDao.class);
        bookDao.update();
    }
}

前置通知

修改MyAdvice,在before方法上添加@Before注解

@Component
@Aspect
public class MyAdvice {
    @Pointcut("execution(void com.itheima.dao.BookDao.update())")
    private void pt(){}
    
    @Before("pt()")
    public void before() {
        System.out.println("before advice ...");
    }
}

 

后置通知
@Component
@Aspect
public class MyAdvice {
    @Pointcut("execution(void com.itheima.dao.BookDao.update())")
    private void pt(){}
    
    @Before("pt()")
    public void before() {
        System.out.println("before advice ...");
    }
    @After("pt()")
    public void after() {
        System.out.println("after advice ...");
    }
}

 环绕通知

@Component
@Aspect
public class MyAdvice {
    @Pointcut("execution(void com.itheima.dao.BookDao.update())")
    private void pt(){}
    
    @Around("pt()")
    public void around(){
        System.out.println("around before advice ...");
        System.out.println("around after advice ...");
    }
}

 

运行结果中,通知的内容打印出来,但是原始方法的内容却没有被执行。

因为环绕通知需要在原始方法的前后进行增强,所以环绕通知就必须要能对原始操作进行调用

@Component
@Aspect
public class MyAdvice {
    @Pointcut("execution(void com.itheima.dao.BookDao.update())")
    private void pt(){}
    
    @Around("pt()")
    public void around(ProceedingJoinPoint pjp) throws Throwable{
        System.out.println("around before advice ...");
        //表示对原始操作的调用
        pjp.proceed();
        System.out.println("around after advice ...");
    }
}

这里的proceed()抛了一个异常,那么进入到源码中就可以直到原因了:

再次运行,就可以看到原始方法已经被执行了

 

因为环绕通知是可以控制原始方法执行的,所以我们把增强的代码写在调用原始方法的不同位置就可以实现不同的通知类型的功能,如:

 通知类型总结:

名称@After
类型方法注解
位置通知方法定义上方
作用设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法后运行
名称@AfterReturning
类型方法注解
位置通知方法定义上方
作用设置当前通知方法与切入点之间绑定关系,当前通知方法在原始切入点方法正常执行完毕后执行
名称@Around
类型方法注解
位置通知方法定义上方
作用设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法前后运行
名称@AfterThrowing
类型方法注解
位置通知方法定义上方
作用设置当前通知方法与切入点之间绑定关系,当前通知方法在原始切入点方法运行抛出异常后执行

环绕通知注意事项  

  1. 环绕通知必须依赖形参ProceedingJoinPoint才能实现对原始方法的调用,进而实现原始方法调用前后同时添加通知

  2. 通知中如果未使用ProceedingJoinPoint对原始方法进行调用将跳过原始方法的执行

  3. 对原始方法的调用可以不接收返回值,通知方法设置成void即可,如果接收返回值,最好设定为Object类型

  4. 原始方法的返回值如果是void类型,通知方法的返回值类型可以设置成void,也可以设置成Object

  5. 由于无法预知原始方法运行后是否会抛出异常,因此环绕通知方法必须要处理Throwable异常

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

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

相关文章

佑友防火墙后台命令执行漏洞

漏洞描述 佑友防火墙 后台维护工具存在命令执行&#xff0c;由于没有过滤危险字符&#xff0c;导致可以执行任意命令 漏洞复现 访问url 使用弱口令登录佑友防火墙后台 User: admin Pass: hicomadmin 点击系统管理 维护工具 Ping 输入可执行命令 127.0.0.1|cat /etc/passwd

【高级程序设计语言C++】AVL树

1. AVL树的概念2. AVL树的旋转2.1. 左单旋2.2 右单旋2.3 左右双旋2.4 右左双旋 1. AVL树的概念 AVL树是一种自平衡二叉搜索树&#xff0c;它在每次插入或删除节点时自动调整以保持树的平衡。AVL树的平衡是通过节点的高度差来衡量的&#xff0c;即左子树的高度和右子树的高度之…

Gartner:2022年全球IaaS公有云服务市场增长30%,首次突破1000亿美元

根据Gartner的统计结果&#xff0c;2022年全球基础设施即服务&#xff08;IaaS&#xff09;市场从2021年的928亿美元增长到1203亿美元&#xff0c;同比增长29.7%。亚马逊在2022年继续排在IaaS市场的第一名&#xff0c;其次是微软、阿里巴巴、谷歌和华为。 最新消息&#xff0c;…

Spring Cloud+Spring Boot+Mybatis+uniapp+前后端分离实现知识付费平台免费搭建 qt

&#xfeff;Java版知识付费源码 Spring CloudSpring BootMybatisuniapp前后端分离实现知识付费平台 提供职业教育、企业培训、知识付费系统搭建服务。系统功能包含&#xff1a;录播课、直播课、题库、营销、公司组织架构、员工入职培训等。 提供私有化部署&#xff0c;免费售…

无涯教程-Lua - 文件I/O

I/O库用于在Lua中读取和处理文件。 Lua中有两种文件操作&#xff0c;即隐式(Implicit)和显式(Explicit)操作。 对于以下示例&#xff0c;无涯教程将使用例文件test.lua&#xff0c;如下所示。 -- sample test.lua -- sample2 test.lua 一个简单的文件打开操作使用以下语句。…

计算机毕设 深度学习猫狗分类 - python opencv cnn

文章目录 0 前言1 课题背景2 使用CNN进行猫狗分类3 数据集处理4 神经网络的编写5 Tensorflow计算图的构建6 模型的训练和测试7 预测效果8 最后 0 前言 &#x1f525; 这两年开始毕业设计和毕业答辩的要求和难度不断提升&#xff0c;传统的毕设题目缺少创新和亮点&#xff0c;往…

vscode 第一个文件夹在上一层文件夹同行,怎么处理

我的是这样的 打开终端特别麻烦 解决方法就是 打开vscode里边的首选项 进入设置 把Compact Folders下边对勾给勾掉

acwing 1064 小国王 线性状态压缩DP

输入 3 2输出 16&#x1f37a; AC code #include<iostream> #include<cstring> #include<cstdio> #include<algorithm> #include<vector>using namespace std;typedef long long ll; const int N 12; const int M 1 << 10, K 110;//…

Openlayers:DWS-DD坐标形式互相转换

参考google地图, 坐标分为DD和DMS两种形式。 在本示例中,DWS-DD做互相转换。Openlayers处理DD-》DMS很简单,逆向操作需要做反向推导,方法参考源代码。 效果图 源代码 /* * @Author: 大剑师兰特(xiaozhuanlan),还是大剑师兰特(CSDN) * @此源代码版权归大剑师兰特所有…

Linux【网络基础】IP协议

文章目录 一、IP协议&#xff08;1&#xff09;IP地址协议概念和理解&#xff08;2&#xff09;IP地址协议格式&#xff08;3&#xff09;网络号和主机号&#xff08;4&#xff09;地址管理&#xff08;一&#xff09;分配IP地址方法&#xff08;二&#xff09;CIDR分配IP地址&…

基本频谱分析

基本频谱分析 傅里叶变换是用于对时域信号执行频率和功率谱分析的工具。 频谱分析数量 频谱分析研究非均匀采样的离散数据中包含的频谱。傅里叶变换是通过在频率空间表示基于时间或空间的信号来揭示该信号的频率分量的工具。下表列出了用于描述和解释信号属性的常用量。要了…

Axios GET 请求:从入门到实践

在进行网络请求时&#xff0c;axios 是一个非常常用的请求库。本文将介绍如何使用 axios 发起 GET 请求&#xff0c;并详细列出传参的几种写法。同时会提供一个实践案例&#xff0c;其中包含基本路由与请求处理的过程&#xff0c;并确保在 IDE 编辑器中可以顺利运行。 什么是 a…

STM32入门——GPIO输入输出

GPIO简介 GPIO&#xff08;General Purpose Input Output&#xff09;通用输入输出口 可配置为8种输入输出模式引脚电平&#xff1a;0V~3.3V&#xff0c;部分引脚可容忍5V输出模式下可控制端口输出高低电平&#xff0c;用以驱动LED、控制蜂鸣器、模拟通信协议输出时序等输入模…

ES6 数组的用法

1. forEach() 用来循环遍历的 for 数组名.forEach(function (item,index,arr) {})item:数组每一项 , index : 数组索引 , arr:原数组作用: 用来遍历数组 let arr [1, 2, 3, 4]; console.log(arr); let arr1 arr.forEach((item, index, arr) > {console.log(item, index…

SpringBoot第29讲:SpringBoot集成MySQL - MyBatis-Plus代码自动生成

SpringBoot第29讲&#xff1a;SpringBoot集成MySQL - MyBatis-Plus代码自动生成 本文是SpringBoot第29讲&#xff0c;主要介绍 MyBatis-Plus代码自动生成&#xff0c;以及产生此类代码生成工具的背景和此类工具的基本实现原理。 文章目录 SpringBoot第29讲&#xff1a;SpringBo…

em3288 linux_4.19 第一次烧写无法进入内核的情况

1. 情况一&#xff1a; /DDR Version 1.11 20210818 In SRX Channel a: DDR3 400MHz Bus Width32 Col10 Bank8 Row15 CS1 Die Bus-Width16 Size1024MB Channel b: DDR3 400MHz Bus Width32 Col10 Bank8 Row15 CS1 Die Bus-Width16 Size1024MB OUT Boot1 Release Time: Jul 22 2…

pyecharts包的简单使用

pyecharts简介 Pyecharts是一个Python的数据可视化库。 它基于ECharts&#xff0c;一个由百度开发的流行的JavaScript图表库。Pyecharts旨在为Python用户提供一种简便的方法来创建各种类型的交互式图表&#xff0c;包括折线图、柱状图、散点图、饼图、地图等。通过使用Pyechar…

场景库之高精度地图编辑器

一、背景介绍 高精度地图编辑器是场景库生产所需的必要工具&#xff0c;地图编辑器基于JS开发&#xff0c;可对指定的地图进行描绘&#xff0c;生成数字高精度地图。 二、功能介绍 路网元素支持&#xff1a; 类别元素图片交叉口交叉口安全岛交通岛导流岛道路中心圈路口边缘线…