spring-第十三章 AOP

news2024/11/8 14:50:42

spring


文章目录

  • spring
  • 前言
  • 1.AOP介绍
  • 2.AOP七大术语
  • 3.切点表达式
  • 4.使用spring的AOP
    • 4.1概述
    • 4.2准备工作
    • 4.3基于注解方式使用AOP
      • 4.3.1准备目标类和目标方法
      • 4.3.2编写配置类
      • 4.3.3编写通知类
      • 4.3.4编写测试类
      • 4.3.5通知类型
      • 4.3.6切面的先后顺序
      • 4.3.7@PointCut注解通用切点
    • 4.4基于XML方式使用AOP
    • 4.5AOP案例:事务处理
    • 4.6AOP案例:安全日志
  • 总结


前言

介绍完代理模式后,我们来看看它在spring中的应用——AOP。


1.AOP介绍

一般一个系统当中都会有一些系统服务,例如:日志、事务管理、安全等。这些系统服务被称为:交叉业务

这些交叉业务几乎是通用的,不管你是做银行账户转账,还是删除用户数据。日志、事务管理、安全,这些都是需要做的。

如果在每一个业务处理过程当中,都掺杂这些交叉业务代码进去的话,存在两方面问题:

  • 第一:交叉业务代码在多个业务流程中反复出现,显然这个交叉业务代码没有得到复用。并且修改这些交叉业务代码的话,需要修改多处。
  • 第二:程序员无法专注核心业务代码的编写,在编写核心业务代码的同时还需要处理这些交叉业务。

使用AOP可以很轻松的解决以上问题。

请添加图片描述

用一句话总结AOP:将与核心业务无关的代码(交叉业务)独立的抽取出来,形成一个独立的组件,然后以横向交叉的方式应用到业务流程当中的过程被称为AOP。

AOP的优点:

  • 第一:代码复用性增强。
  • 第二:代码易维护。
  • 第三:使开发者更关注业务逻辑。

上一章中介绍JDK动态代理和CGLIB动态代理,是因为spring中就是用这两个技术实现AOP的
Spring的AOP使用的动态代理是:JDK动态代理 + CGLIB动态代理技术。Spring在这两种动态代理中灵活切换,如果是代理接口,会默认使用JDK动态代理,如果要代理某个类,这个类没有实现接口,就会切换使用CGLIB。当然,你也可以强制通过一些配置让Spring只使用CGLIB。

2.AOP七大术语

在使用spring的AOP之前,我们需要先知道AOP中的七个概念:

  • 连接点:在整个业务流程中,可以插入额外功能的****位置
  • 切点:如果我们为原业务的某方法前或后插入新功能,则该方法就是切点。即,切点是我们将插入额外功能的原方法。
  • 通知:指的是我们具体的我们要添加的额外功能。根据插入位置的不同可以分为:前置通知、后置通知、环绕通知、异常通知、最终通知。
  • 切面切点+通知就是一个切面,也就是原方法与额外功能结合形成的新逻辑。
  • 织入:把通知应用到目标对象上的过程。
  • 代理对象:由目标对象织入通知后产生的新对象。
  • 目标对象:被织入通知的对象。

3.切点表达式

AOP会对原有的方法进行功能增强,那么我们在代码中如何找到要添加功能的原方法?答案就是切点表达式。
切点表达式用来定义通知(Advice)往哪些方法上切入,其格式如下:
execution([访问控制权限修饰符] 返回值类型 [全限定类名]方法名(形式参数列表) [异常])

访问权限控制符:

  • 可选项
  • 省略就是四个权限(private、protected、default、public)都包括
  • 可以填写具体权限符表示只应用于权限等级匹配的方法

返回值类型:

  • 必填项
  • 填写*****表示任意返回类型

全限定类名:

  • 因为可能存在同名方法,所以可以额外填写全限定类名进一步确认
  • 可选项
  • …表示范围为当前包以及子包下的所有类
  • 省略表示所有的类

方法名:

  • 必填项
  • *****表示所有方法
  • **set***表示所有set方法

形式参数列表

  • 必填项
  • ()表示无参方法
  • (…)表示任意类型、任意个数参数
  • (*)表示只有一个参数
  • (*,String)表示第一个参数任意,第二个参数是字符串

异常

  • 可选项
  • 省略表示任意异常类型

下面来举几个具体的例子:

service包下所有类中以delete开始的所有方法
execution(public * com.powernode.mall.service..delete(…))

mall包下所有类的所有方法
execution(* com.powernode.mall…*(…))

所有类的所有方法
execution(* *(…))

4.使用spring的AOP

4.1概述

Spring对AOP的实现包括以下3种方式:

  • 第一种方式:Spring框架结合AspectJ框架实现的AOP,基于注解方式。
  • 第二种方式:Spring框架结合AspectJ框架实现的AOP,基于XML方式。
  • 第三种方式:Spring框架自己实现的AOP,基于XML配置方式。

实际开发中,都是Spring+AspectJ来实现AOP。所以我们重点学习第一种和第二种方式。

4.2准备工作

要使用AOP功能,我们先要导入aop和aspects的依赖,同时为了保证AOP功能的完善可以额外导入aspectjweaver依赖包。
所以我们需要导入以下依赖包:

    org.springframework  
    spring-context  
    6.1.12  
  
  
    org.springframework  
    spring-aspects  
    6.1.10  
  
  
    org.aspectj  
    aspectjweaver  
    1.9.22.1  
  
  
    org.junit.jupiter  
    junit-jupiter  
    RELEASE  
    test

4.3基于注解方式使用AOP

4.3.1准备目标类和目标方法

先准备我们需要使用的目标类和目标方法,这里准备了一个OrderService类,且其中有一个generate()方法作为目标方法。代码如下:

package org.example.service;  
  
import org.springframework.stereotype.Service;  
  
//目标类  
@Service("orderService")  
public class OrderService {  
//    目标方法  
    public void generate(){  
        System.out.println("订单已生成!");  
    }  
}

需要使用注解让该类能够被IOC容器管理,这一步不要忽略。

4.3.2编写配置类

因为是使用注解的方式来进行使用,所以我们需要提供配置类来代替配置文件,配置类代码如下:

package org.example.conf;  
  
import org.springframework.context.annotation.ComponentScan;  
import org.springframework.context.annotation.Configuration;  
import org.springframework.context.annotation.EnableAspectJAutoProxy;  
  
@Configuration  
@EnableAspectJAutoProxy(proxyTargetClass = true)  
@ComponentScan({"org.example.service","org.example.aspect","org.example.conf"})  
public class AspectConf {  
}
  • 添加**@Configuration**表示该类为配置类
  • 使用**@EnableAspectJAutoProxy注解开启自动动态代理功能。前面说过spring中使用了JDK动态代理和CGLIB动态代理。这里设置proxyTargetClass**属性值为true——表示指定使用CGLIB动态代理。如果不设置,默认值为false——表示在代理接口时使用JDK代理,代理类时使用CGLIB。
  • 使用**@ComponentScan**注解添加包扫描路径,让IOC能够正确扫描我们所有需要的类并管理成bean

4.3.3编写通知类

我们需要把通知(增强功能)放到通知类里面,并在里面进行设置,决定该通知究竟要为哪一个目标方法进行补充,以及这些通知具体要在什么时候执行。
具体代码如下:

package org.example.aspect;  
  
import org.aspectj.lang.JoinPoint;  
import org.aspectj.lang.annotation.After;  
import org.aspectj.lang.annotation.Aspect;  
import org.aspectj.lang.annotation.Before;  
import org.aspectj.lang.annotation.Pointcut;  
import org.springframework.stereotype.Component;  
  
  
  
@Component  
@Aspect  
public class LogAspect {  
    
  
    @Before("execution(* org.example.service.OrderService.generate())")  
    public void before(JoinPoint joinPoint) {  
        System.out.println("前置通知");  
        System.out.println("Before " + joinPoint.getSignature().getName());  
    }  
    @After("execution(* org.example.service.OrderService.generate())")  
    public void after(JoinPoint joinPoint) {  
        System.out.println("后置通知");  
    }  
}
  • 使用**@Component**注解将通知类管理成bean
  • 使用**@Aspect**注解把当前类标注为一个通知类
  • 使用**@Before@After**注解把方法标注为一个通知,同时决定其相较目标方法的执行时机,像这样能够标注通知并决定执行时机的注解共有五个后面会具体说。
  • 在@Before和@After注解的参数中书写切入点表达式,确认当前通知作用于哪些目标方法。

4.3.4编写测试类

我们编写测试类,来验证当我们执行目标方法时,通知中的方法是否也会执行。
单元测试代码如下:

import org.example.conf.AspectConf;  
import org.example.service.OrderService;  
import org.junit.jupiter.api.Test;  
import org.springframework.context.annotation.AnnotationConfigApplicationContext;  
  
public class AspectTest {  
    @Test  
    public void testAspect() {  
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AspectConf.class);  
        OrderService orderService = context.getBean("orderService", OrderService.class);  
        orderService.generate();  
    }  
}

执行结果
请添加图片描述

执行结果正确。

4.3.5通知类型

前面我们使用@Before和@After注解来标注通知,但其实我们还可以用其他注解来标注通知。如下:

  • 前置通知:@Before 目标方法执行之前的通知
  • 后置通知:@AfterReturning 目标方法执行之后的通知
  • 环绕通知:@Around 目标方法之前添加通知,同时目标方法执行之后添加通知。
  • 异常通知:@AfterThrowing 发生异常之后执行的通知
  • 最终通知:@After 放在finally语句块中的通知

环绕通知
其他通知类型都是直接在方法上添加注解后在方法体内编写增强逻辑即可,但是环绕通知需要在编写增强逻辑的过程中调用目标方法,这里重点说一说。
环绕通知中我们有一个参数——ProceedingJoinPoint类型参数,该参数对象中有一个proceed()方法能够让我们在编写增强逻辑时调用目标方法,由此来决定环绕通知中各个额外功能代码相对于目标方法的位置。
如下:

@Around("execution(* org.example.service.OrderService.generate())")  
    public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {  
        System.out.println("环绕通知开始");  
        // 执行目标方法。  
        proceedingJoinPoint.proceed();  
        System.out.println("环绕通知结束");  
    }

最终的运行效果就会让环绕通知的代码分开在目标方法的前后执行
请添加图片描述

执行顺序
各种不同类型的通知的执行顺序如下:
无异常时:环绕通知前部分-》前置通知-》目标方法-》后置通知-》最终通知-》环绕通知后部分
有异常时:环绕通知前部分-》前置通知-》目标方法-》异常通知-》最终通知

4.3.6切面的先后顺序

前面研究了不同通知类型的运行顺序,但那只是针对单个通知类的情况,当有多个通知类时不同类之间的不同通知又会按照什么顺序执行?
当拥有多个通知类时,我们可以在通知类上使用**@Order**注解来指定它们之间的执行顺序。

为@Order注解的value指定一个整数型的数字,数字越小,优先级越高。

假设有现在有两个通知类:LogAspect、LogAspect2,
其代码如下:
LogAspect

package org.example.aspect;  
  
import org.aspectj.lang.JoinPoint;  
import org.aspectj.lang.ProceedingJoinPoint;  
import org.aspectj.lang.annotation.*;  
import org.springframework.core.annotation.Order;  
import org.springframework.stereotype.Component;  
  
  
  
@Component  
@Aspect  
@Order(1)  
public class LogAspect {  
    @Pointcut("execution(* org.example.service.OrderService.generate())")  
    public void pt(){}  
  
    @Before("pt()")  
    public void before(JoinPoint joinPoint) {  
        System.out.println("前置通知,Aspect1");  
    }  
    @After("pt()")  
    public void after(JoinPoint joinPoint) {  
        System.out.println("最终通知,Aspect1");  
    }  
    @Around("pt()")  
    public void around(ProceedingJoinPoint joinPoint) throws Throwable {  
        System.out.println("环绕通知开始,Aspect1");  
        // 执行目标方法。  
        joinPoint.proceed();  
        System.out.println("环绕通知结束,Aspect1");  
    }  
    @AfterReturning("pt()")  
    public void afterReturning(JoinPoint joinPoint) {  
        System.out.println("后置通知,Aspect1");  
    }  
  
  
}

LogAspect2

package org.example.aspect;  
  
import org.aspectj.lang.JoinPoint;  
import org.aspectj.lang.ProceedingJoinPoint;  
import org.aspectj.lang.annotation.*;  
import org.springframework.core.annotation.Order;  
import org.springframework.stereotype.Component;  
  
@Component  
@Aspect  
@Order(2)  
public class LogAspect2 {  
    @Pointcut("execution(* org.example.service.OrderService.generate())")  
    public void pt(){}  
    @Before("pt()")  
    public void before(JoinPoint joinPoint) {  
        System.out.println("前置通知,Aspect2");  
    }  
    @After("pt()")  
    public void after(JoinPoint joinPoint) {  
        System.out.println("最终通知,Aspect2");  
    }  
    @Around("pt()")  
    public void around(ProceedingJoinPoint joinPoint) throws Throwable {  
        System.out.println("环绕通知开始,Aspect2");  
        // 执行目标方法。  
        joinPoint.proceed();  
        System.out.println("环绕通知结束,Aspect2");  
    }  
    @AfterReturning("pt()")  
    public void afterReturning(JoinPoint joinPoint) {  
        System.out.println("后置通知,Aspect2");  
    }  
}

我们分别使用@Order注解设置了优先级:LogAspect(1),LogAspect2(2)。
运行后结果如下
请添加图片描述

4.3.7@PointCut注解通用切点

在前面的例子中,我们明明所有通知都是作用于同一个目标方法,但是却要在每个通知上方都分别写同样的切点表达式。这样太麻烦。
于是spring中提供**@PointCut**注解,让我们遇到多个通知需要作用于同一个目标方法的情况下,只要写一次切点表达式即可。
请添加图片描述

  1. 单独用一个方法来使用@Pointcut注解,并书写切点表达式
  2. 其他通知原本写切点表达式的地方换为传入使用了@Pointcut注解的方法

4.4基于XML方式使用AOP

这种方式比较麻烦,也少有人用,这里先埋坑。

4.5AOP案例:事务处理

事务的工作十分适合使用AOP进行处理,比如事务开启操作可以由一个前置通知完成,事务提交可以用后置通知完成,最后还可以使用异常通知来进行事务回滚,这样一来很好的解决了事务代码和业务代码杂糅的问题。
这种我们自己使用AOP功能并编写事务代码来完成事务管理的方式,就是编程式事务管理
而后面spring提供了一系列注解和xml配置项来完成事务功能,这种叫做声明式事务管理

4.6AOP案例:安全日志

需求是这样的:项目开发结束了,已经上线了。运行正常。客户提出了新的需求:凡事在系统中进行修改操作的,删除操作的,新增操作的,都要把这个人记录下来。因为这几个操作是属于危险行为。


总结

本章我们介绍了spring中的重要概念——AOP

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

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

相关文章

jmeter常用配置元件介绍总结之安装插件

系列文章目录 1.windows、linux安装jmeter及设置中文显示 2.jmeter常用配置元件介绍总结之安装插件 3.jmeter常用配置元件介绍总结之取样器 jmeter常用配置元件介绍总结之安装插件 1.下载插件2.安装插件管理包3.不用插件管理包,直接官网插件下载安装 1.下载插件 jm…

MySQL 多数据库备份与恢复,包括查询,函数,SP

一、备份语句: mysqldump 可以用来导出单个数据库、多个数据库,甚至所有数据库的数据。以下是导出多个数据库到指定文件位置的语法和具体案例。 基本语法 mysqldump -u [username] -p[password] --databases [db1] [db2] ... > [file_path] -u: …

contenteditable实现需要一个像文本域一样的可编辑框

我这里是因为左上和右下有一个固定的模板,所有用textarea有点不方便,查了下还有一个方法可以解决就是在需要编辑的元素上加上 :contenteditable"true" 完整代码如下,因为这个弹窗是两用的,所以用messageType做了一下判…

linux 安装anaconda3

1.下载 使用repo镜像网址下载对应安装包 右击获取下载地址,使用终端下载 wget https://repo.anaconda.com/archive/Anaconda3-2024.02-1-Linux-x86_64.sh2.安装 使用以下命令可直接指定位置 bash Anaconda3-2024.02-1-Linux-x86_64.sh -b -p /home/anaconda3也…

如何选择适合的AWS EC2实例类型

在云计算的世界中,Amazon Web Services(AWS)提供了丰富的服务,其中Elastic Compute Cloud(EC2)是最受欢迎的服务之一。选择合适的EC2实例类型对于确保应用程序的性能和成本效益至关重要。我们九河云通过本文…

(蓝桥杯C/C++)——基础算法(下)

目录 一、时空复杂度 1.时间复杂度 2.空间复杂度 3.分析技巧 4.代码示例 二、递归 1.递归的介绍 2.递归如何实现 3.递归和循环的比较 4.代码示例 三、差分 1.差分的原理和特点 2.差分的实现 3.例题讲解 四、枚举 1.枚举算法介绍 2.解空间的类型 3. 循环枚举解…

7.5 inch电力线载波通信技术

7.5寸电子桌牌 产品型号 PE75R_D_W 尺寸 176.2*137.15*80mm 屏幕尺寸 7.5 inch 显示区域(mm) 163.2(H) * 97.92(V) 分辨率 800*480 显示技术 电子墨水屏双面显示 显示颜色 黑/白/红 外观颜色 银色 工作温度 0-40℃ 视角 180 支持内容格式 文本/图片/二维码…

Linux下的ADC

ADC ADC简介 ADC是 Analog Digital Converter 的缩写,翻译过来为模数转换器,ADC可以将模拟值转换成数字值。模拟值是什么呢?比如我们日常生活中的温度,速度,湿度等等都是模拟值。所以如果我们想测量这些模拟值的值是多少&#x…

星空天文 2.0.1| 完全免费的观星软件,无注册登录,天文爱好者必备。

星空天文是一款完全免费且功能强大的观星软件,适用于安卓平台。无需注册登录即可使用,界面设计精美且操作简单。软件支持AR实景模式,可以将实景与星空结合,增强观星体验。用户可以设定任意日期和时间来观察不同时段的天空&#xf…

书生大模型实战营第四期-入门岛-1. Linux前置基础

入门岛-Linux前置基础 书生大模型实战营-第四期-Linux前置基础: 任务:https://github.com/InternLM/Tutorial/blob/camp4/docs/L0/linux/task.md 文档:https://github.com/InternLM/Tutorial/tree/camp4/docs/L0/linux 任务描述完成所需时…

JavaEE初阶--servlet篇(三)HttpServlet/response/request对应方法使用

文章目录 1.总括说明2.httpservlet父类2.1方法介绍2.2dopost方法的演示2.3doput方法的演示 3.HttpServletRequest类3.1方法说明3.2方法使用演示3.3getparameter方法使用3.4使用form表单的方式3.5jackson获取参数 4.HttpResponse类4.1设置状态码4.2自动进行刷新4.3重定向跳转4.3…

前后端分离,Jackson,Long精度丢失

案例:后端接口放回一个Long数据 GetMapping("/testForLong")public Map<String, Object> testForLong() {Map<String, Object> map new HashMap<>();map.put("aaa", 1234567890123456789L);return map;}实际前端接收的数据 前后端数据…

记某单位众测项目漏洞挖掘中的一些手法

&#x1f36c; 博主介绍 &#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;我是 一个想当文人的黑客 &#xff0c;很高兴认识大家~ ✨主攻领域&#xff1a;【渗透领域】【应急响应】 【edusrc漏洞挖掘】 【VulnHub靶场复现】【面试分析】 &#x1f389;欢迎…

用 Python 写了一个天天酷跑(附源码)

Hello&#xff0c;大家好&#xff0c;给大家说一下&#xff0c;我要开始装逼了 这期写个天天酷跑玩一下叭&#xff01; 制作一个完整的“天天酷跑”游戏涉及很多方面&#xff0c;包括图形渲染、物理引擎、用户输入处理、游戏逻辑等。由于Python是一种高级编程语言&#xff0c;…

芯片设计公司ERP系统如何实现一体化管理

在当今高科技迅猛发展的时代&#xff0c;芯片设计行业作为信息技术的核心&#xff0c;正面临着日益激烈的市场竞争和复杂多变的市场需求。为了提升企业的运营效率和市场竞争力&#xff0c;芯片设计公司纷纷引入ERP(企业资源计划)系统&#xff0c;以实现一体化管理。接下来我们跟…

50岁+人群月活超1亿,短剧迎来新对手,小程序游戏“收割”中老年

抢夺中老年流量&#xff1a;微短剧向左&#xff0c;小游戏向右 作者&#xff5c;AgeClub 干货抢先看 1.《黑神话&#xff1a;悟空》走红&#xff0c;吸引大量玩家入坑单机市场。与硬核单机游戏不同&#xff0c;在渗透率更高的小游戏领域&#xff0c;聚集了更多“网瘾”中老年…

手机如何打开chm文件

chm文件一般是帮助文档&#xff0c;手机一般不能直接打开&#xff0c;我们可以通过下载阅读器来打开 以荣耀手机为例 首先下载掌阅iReaderAPP 下载完成后打开掌阅 点击书架&#xff0c;右上角本机导入 搜索你下载的chm文件的名字 勾选&#xff0c;加入书架(应该保留目录) 在书…

《重学Java设计模式》之 工厂方法模式

《重学Java设计模式》之 建造者模式 《重学Java设计模式》之 原型模式 《重学Java设计模式》之 单例模式 模拟发奖多种商品 工程结构 奖品发放接口 package com.yys.mes.design.factory.store;public interface ICommodity {/*** Author Sherry* Date 14:20 2024/11/6**/voi…

【算法与数据结构】【链表篇】【题1-题5】

题1.从尾到头打印链表 题目&#xff1a;输入一个链表的头结点&#xff0c;从尾到头反过来打印出每个节点的值。链表的定义如下&#xff1a; struct ListNode {int mValue;ListNode *mNext;ListNode *mPrev; }; 1.1 方法一&#xff1a;栈 思路&#xff1a;要反过来打印&…

28.医院管理系统(基于springboot和vue)

目录 1.系统的受众说明 2. 相关技术和开发环境 2.1 相关技术 2.1.1 Java语言 2.1.2 HTML、CSS、JavaScript 2.1.3 Redis 2.1.4 MySQL 2.1.5 SSM框架 2.1.6 Vue.js 2.1.7 SpringBoot 2.2 开发环境 3. 系统分析 3.1 可行性分析 3.1.1 经济可行性 3.1.2 技术…