spring AOP详解

article/2025/8/11 20:42:04

文章目录

    • AOP
      • 1 环境准备
        • 1.1 工程及接口创建
        • 1.2 工程存在的问题
          • 1.2.1 问题
          • 1.2.2 解决思路
      • 2 AOP面向切面编程
        • 2.1 AOP概述
        • 2.2 AOP原理分析
      • 3 基于注解的AOP
        • 3.1 入门示例
        • 3.2 使用流程
        • 3.3 切入点表达式
        • 3.4 练习
        • 3.5 通知类型

AOP

​ AOP(Aspect Orient Programming)是一种设计思想,是软件设计领域中的面向切面编程,它是面向对象编程(OOP)的一种补充和完善;

​ 实际项目中我们通常将面向对象理解为一个静态过程(例如一个系统有多少个模块,一个模块有哪些对象,对象有哪些属性),面向切面理解为一个动态过程(例如,在对象运行时动态织入一些扩展功能或控制对象执行)。

1 环境准备

1.1 工程及接口创建
  • 第1步:创建普通 SpringBoot 工程 _07springaop

  • 第2步:创建计算器接口 Calculator ,并定义 加减乘除 4 个方法;

    package cn.tedu._07springaop.aop;public interface Calculator {int add(int m, int n);int sub(int m, int n);int mul(int m, int n);int div(int m, int n);
    }
    
  • 第3步:创建实现类 CalculatorImpl,并实现这 4 个方法,要带有日志功能;

    package cn.tedu._07springAop.aop;public class CalculatorImpl implements Calculator{@Overridepublic int add(int m, int n) {System.out.println("[日志]add方法开始..." );int result = m + n;System.out.println("[日志]add方法结束...);return result;}@Overridepublic int sub(int m, int n) {System.out.println("[日志]sub方法开始... );int result = m - n;System.out.println("[日志]sub方法结束...);return result;}@Overridepublic int mul(int m, int n) {System.out.println("[日志]mul方法开始... );int result = m * n;System.out.println("[日志]mul方法结束...);return result;}@Overridepublic int div(int m, int n) {System.out.println("[日志]div方法开始...");int result = m / n;System.out.println("[日志]div方法结束...");return result;}
    }
    
1.2 工程存在的问题
1.2.1 问题

​ 附加功能分散在各个业务功能方法中,不利于统一维护

1.2.2 解决思路
  • 解耦:将附加功能代码从业务功能代码中抽取出来;
  • 难点:要抽取的代码在方法内部,使用之前技术不太好解决,而AOP技术可以解决这个难题。

2 AOP面向切面编程

2.1 AOP概述

​ AOP(Aspect Oriented Programming)是一种设计思想,是软件设计领域中的面向切面编程,它是面向对象编程的一种补充和完善;

​ 在不修改源代码的情况下,给程序动态统一添加额外功能的一种技术。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

2.2 AOP原理分析
  • 问题思考

    假如现在有一个业务对象,这个对象已经实现了一些核心业务功能,但是我们希望在核心业务的基础上在添加一些拓展业务,而且要求不能对目标业务对象中的实现进行修改(遵循OCP原则-对扩展开放,对修改关闭),请问如何实现?

  • 解决思路

    为目标类创建子类或者为目标类创建兄弟类,对目标业务进行功能拓展。这种方式可以实现,但如果需要进行业务拓展的类有很多,我们每个类都要基于目标类型进行子类或兄弟类的创建,工作量会比较大;

    AOP可以在系统启动时为目标类型创建子类或兄弟类型对象,这样的对象我们通常会称之为动态代理对象.如图:

    在这里插入图片描述

    其中,为目标类型(XxxServiceImpl)创建其代理对象方式有两种(先了解):

    • 第一种方式:借助JDK官方API(Proxy)为目标对象类型创建其兄弟类型对象;

    • 第二种方式:借助CGLIB库为目标对象类型创建其子类类型对象.

3 基于注解的AOP

​ 基于注解的AOP是一种AOP的实现方式,它通过在Java类、方法、参数等上添加注解的方式来实现切面的定义和应用,相比于传统的XML配置方式更加便捷和灵活.

3.1 入门示例
  • 第1步:添加依赖

    <!--引入aop依赖-->
    <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
    
  • 第2步:准备被代理的目标资源

    接口

    package cn.tedu.aop;public interface Calculator {int add(int m, int n);int sub(int m, int n);int mul(int m, int n);int div(int m, int n);
    }
    

    实现类

    package cn.tedu._07springAop.aop;import org.springframework.stereotype.Component;@Component
    public class CalculatorImpl implements Calculator{@Overridepublic int add(int m, int n) {int result = m + n;System.out.println("方法内部:" + result);return result;}@Overridepublic int sub(int m, int n) {int result = m - n;System.out.println("方法内部:" + result);return result;}@Overridepublic int mul(int m, int n) {int result = m * n;System.out.println("方法内部:" + result);return result;}@Overridepublic int div(int m, int n) {int result = m / n;System.out.println("方法内部:" + result);return result;}
    }
  • 第3步:创建切面类并配置 LogAspect

    用于进行切入点的定义,功能增强方法的定义,在方法内部做日志业务增强,关键代码如下:

    package cn.tedu._07springAop.aop;import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.springframework.stereotype.Component;/*** Aspect注解:表示这个类是一个切面类*/
    @Aspect
    @Component
    public class LogAspect {@Before("execution(public int cn.tedu._07springAop.aop.CalculatorImpl.*(..))")public void beforeMethod(JoinPoint joinPoint) {System.out.println("LogAspect类中:前置通知,方法开始记录日志了...");}
    }
  • 第4步:测试类中进行测试

    @SpringBootTest
    class ApplicationTests {@Testvoid contextLoads() {ApplicationContext context = new AnnotationConfigApplicationContext("cn.tedu._07springAop");Calculator calculator = context.getBean(Calculator.class);int addResult = calculator.add(10, 20);}
    }
    
  • 第5步:执行结果

    在这里插入图片描述

3.2 使用流程
  • 第1步:添加 aop 依赖并刷新 Maven

    <!--引入aop依赖-->
    <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
    
  • 第2步:创建切面类(选择通知的方式);

    注意添加 @Aspect 注解,表示该类为一个切面类.

3.3 切入点表达式

​ 在 AOP 中,切入点表达式指定了哪些方法需要被植入增强逻辑。它是一个表达式,用于匹配目标对象中的方法,并提供切入点的精确定义

在这里插入图片描述

  • 切入点表达式语法
    • 权限修饰符:* 表示权限修饰符不限;
    • 方法返回值:* 表示返回值不限;
    • 包名部分: *… 表示包名任意,包的层次和深度任意;
    • 类名部分: * 表示类名任意;
    • 方法名部分:* 表示方法名任意;
    • 方法参数列表部分:(…) 表示参数列表任意;
3.4 练习
  • 项目背景

    假设你有一个OrderService接口,其中有一个方法placeOrder,无返回值。这个方法在执行时,需要先进行订单数量的检查,如果订单数量大于0,则继续执行订单创建,否则返回异常信息,具体异常为:IllegalArgumentException

  • 操作步骤

    • 第1步:创建接口OrderService和接口方法 placeOrder[需要有一个int类型的参数orderNumber];
    • 第2步:创建实现类 OrderServiceImpl,实现该方法,打印:订单创建成功;
    • 第3步:创建切面类 OrderCheckAspect ,前置通知 beforeMethod,进行订单数量的校验
    • 第4步:创建测试类进行测试
  • 要求

    • 请使用AOP来实现上述功能;
    • 给出切面的定义和通知的定义。
3.5 通知类型
  • 前置通知:@Before 在被代理的目标方法执行

  • 返回通知:@AfterReturning 在被代理的目标方法成功结束后执行

  • 后置通知:@After 在被代理的目标方法最终结束后执行

  • 异常通知:@AfterThrowing 在被代理的目标方法异常结束后执行

  • 环绕通知:@Around 使用try…catch…finally结构围绕整个被代理的目标方法,包括上面四种通知对应的所有位置

问题:返回通知 和 后置通知 的区别是什么?

  • 执行时机

    返回通知是在目标方法执行后返回结果时执行的通知,只有目标方法正常返回时才会执行,抛异常则不会执行。后置通知则是在目标方法执行后执行的通知,无论目标方法是否抛出异常,后置通知都会执行。

  • 访问权限

    返回通知可以获取并修改目标方法的返回值,后置通知无法访问目标方法的返回值。

通知类型示例代码

package cn.tedu._07springAop.aop;import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;import java.util.Arrays;/*** Aspect注解:表示这个类是一个切面类*/
@Aspect
@Component
public class LogAspect {@Before("execution(public int cn.tedu._07springAop.aop.CalculatorImpl.*(..))")public void beforeMethod(JoinPoint joinPoint) {/*getSignature(): 获取连接点签名信息;getName(): 获取连接点名称;getArgs(): 获取连接点参数;*/String methodName = joinPoint.getSignature().getName();String args = Arrays.toString(joinPoint.getArgs());System.out.println("LogAspect类中:前置通知,方法开始记录日志了..." + methodName + ":" + args);}@After("execution(public int cn.tedu._07springAop.aop.CalculatorImpl.*(..))")public void aftherMethod(JoinPoint joinPoint){System.out.println("LogAspect类中:后置通知,方法开始记录日志了...");}@AfterReturning(value = "execution(public int cn.tedu._07springAop.aop.CalculatorImpl.*(..))", returning = "result")public void afterReturningMethod(JoinPoint joinPoint, Object result){System.out.println("LogAspect类中:返回通知,方法开始记录日志了...");}@AfterThrowing(value = "execution(public int cn.tedu._07springAop.aop.CalculatorImpl.*(..))", throwing = "ex")public void afterThrowingMethod(JoinPoint joinPoint, Throwable ex){System.out.println("LogAspect类中:异常通知,方法开始记录日志了...");}@Around("execution(public int cn.tedu._07springAop.aop.CalculatorImpl.*(..))")public Object aroundMethod(ProceedingJoinPoint joinPoint){Object result = null;try {System.out.println("环绕通知:目标对象方法执行之前");//目标对象方法的执行result = joinPoint.proceed();System.out.println("环绕通知:目标对象方法执行之后");} catch (Throwable throwable) {throwable.printStackTrace();System.out.println("环绕通知:目标对象方法出现异常");} finally {System.out.println("环绕通知:目标对象方法执行完毕");}return result;}
}

http://www.hkcw.cn/article/tDPZbTiXZl.shtml

相关文章

重看Spring聚焦ApplicationContext分析

目录 一、理解下ApplicationContext的设计 &#xff08;一&#xff09;功能性的理解 &#xff08;二&#xff09;ApplicationContext 结构类图 二、ApplicationContext根接口 &#xff08;一&#xff09;源码展示 &#xff08;二&#xff09;分析说明 三、子接口Configu…

【MySQL安装】—报错“Can‘t connect to local MySQL server through socket ‘varlibmysqlmysql.sock‘”

项目场景&#xff1a; 执行 “mysql -uroot -p” 命令&#xff0c;进入MySQL数据库。 问题描述&#xff1a; 报错&#xff1a; Cant connect to local MySQL server through socket /var/lib/mysql/mysql.sock 原因分析&#xff1a; /var/lib/mysql路径下缺少mysql.sock文件。 …

本地部署Vanna实战,快速解决NLP2SQL

一、背景 ​ 随着DeepSeek的火爆&#xff0c;基于AI的应用也如雨后春笋般迸发出来&#xff0c;如何根据用户的一句话来找到用户所需要的信息&#xff0c;采用传统的方式无法通过模糊匹配等实现复杂的业务场景&#xff0c;故探索一种新的思路来实现信息获取。Text2SQL将自然语言…

【MySQL】基础操作

MySQL(二)基础操作 一、数据库操作 1.创建库 2.查看库 3.选中库 4.删除库 二、表操作 1.创建表 1.1[comment 注释]&#xff1a; 1.2,...&#xff1a; 2.查看表 2.1查看所有表 2.2查看表结构 3.删除表 三、记录操作 1.插入记录 1.1全列插入 1.2指定列插入 1.3…

嵌入式硬件篇---蜂鸣器

蜂鸣器是一种常用的电子发声元件&#xff0c;主要分为有源蜂鸣器和无源蜂鸣器两类。它们在结构、工作原理、驱动方式、应用场景等方面存在显著差异。以下是详细介绍&#xff1a; 一、核心定义与结构差异 1. 有源蜂鸣器 定义&#xff1a; “有源” 指内部自带振荡电路&#x…

工程的焊接技术

一、焊接设备与材料 焊接设备&#xff1a;对应不同焊接方法&#xff0c;如焊条电弧焊设备包括电焊机、焊钳、接地夹等。 焊接材料 焊条 分类&#xff1a;按熔渣性质分为碱性焊条&#xff08;低氢型&#xff09;和酸性焊条。 选用原则&#xff1a;根据焊接场景选择&#xff0c;…

HackMyVM-Teacher

信息搜集 主机发现 ┌──(kali㉿kali)-[~] └─$ nmap -sn 192.168.43.0/24 Starting Nmap 7.95 ( https://nmap.org ) at 2025-06-01 01:02 EDT Nmap scan report for 192.168.43.1 Host is up (0.0084s latency). MAC Address: C6:45:66:05:91:88 (Unknow…

AE矩形工具蒙版找不到椭圆形工具怎么办?

是不是也跟我一样遇到了这个问题 &#xff1f; 还以为是自己安装的版本有问题。其实并没有。 只需要选择矩形工具&#xff0c;鼠标左键&#xff0c;长按1s即可有其他选项 这样就解决啦

Linux 学习-模拟实现【简易版bash】

1、bash本质 在模拟实现前&#xff0c;先得了解 bash 的本质 bash 也是一个进程&#xff0c;并且是不断运行中的进程 证明&#xff1a;常显示的命令输入提示符就是 bash 不断打印输出的结果 输入指令后&#xff0c;bash 会创建子进程&#xff0c;并进行程序替换 证明&#x…

【Android SDK(adb命令环境)工具安装下载教程】

1、打开下载地址&#xff1a;SDK 平台工具版本说明 | Android Studio | Android Developers 2、下载Android SDK Platform-Tools压缩包&#xff0c;选择路径进行解压 3、复制SDK文件platform-tools保存的路径 4、配置adb环境变量&#xff1b;按下wini,在设置界面搜索”环境…

Redis可视化工具 RDM mac安装使用

第一步&#xff1a;https://pan.baidu.com/s/10vpdhw7YfDD7G4yZCGtqQg?at1673701651004将dmg下载 第二部&#xff1a;点击下载的dmg文件进行安装、mac可能会提示&#xff1a; 无法验证此App不包含恶意软件 解决方法&#xff1a; 打开系统偏好设置>安全性与隐私>通用&am…

Mac 使用 Crossover 加载 Windows Steam 游戏库,实现 Windows/Mac 共享移动硬盘

Mac 使用 Crossover 加载 Windows Steam 游戏库&#xff0c;实现 Windows/Mac 共享移动硬盘 1. 在Crossover上安装Steam2. Steam容器加载移动硬盘3. 配置Steam库 前言&#xff1a;本文介绍了如何在Crossover上安装Steam并加载外接移动硬盘&#xff0c;实现在Window上下载的游戏…

Mac上媲美TortoiseSVN 的Svn的强大客户端 — macSvn

什么是macSvn&#xff1f; 如果你使用过 svn 那肯定听说过 TortoiseSVN, 但是 TortoiseSVN 并不支持在 mac 上使用。而 macSvn 是一款专为macOS设计的SVN&#xff08;Subversion&#xff09;客户端,它和TortoiseSVN一样&#xff0c;提供了直观的图形化操作方式.操作非常方便! …

给Android Studio配置本地gradle和maven镜像地址,加快访问速度

Android Studio在创建工程后默认会访问Google自己的官网去下载gradle和maven依赖项&#xff0c;国内访问Google的速度相当慢&#xff0c;如果没有科学上网的话&#xff0c;甚至无法访问。本文记录如何解决这些问题。 配置本地gradle 下载gradle 首先需要去国内的网站下载gra…

Flutter 打包报错:Execution failed for task ‘:flutter_plugin_android_lifecycle的解决办法

本篇文章主要讲解&#xff1a;Flutter 打包报错&#xff1a;Execution failed for task :flutter_plugin_android_lifecycle的解决办法。 日期&#xff1a;2025年2月16日 作者&#xff1a;任聪聪 报错现象&#xff1a; 报文信息&#xff1a; FAILURE:Buildfailedwithexception…

uniapp从入门到精通(全网保姆式教程)~ 别再说你不会开发小程序了

目录 一、介绍 二、环境搭建&#xff08;hello world&#xff09; 2.1 下载HBuilderX 2.2 下载微信开发者工具 2.3 创建uniapp项目 2.4 在浏览器运行 2.5 在微信开发者工具运行 2.6 在手机上运行 三、项目基本目录结构 四、开发规范概述 五、全局配置文件&#xff0…

macOS包管理器HomeBrew的安装和使用(适合小白)

Homebrew 是 macOS 上广受欢迎的包管理器&#xff0c;它让安装、更新、卸载和管理开发工具及应用程序变得非常简单&#xff0c;通过HomeBrew&#xff0c;用户可以快速获取最新版本的软件包&#xff0c;而无需手动下载和安装。本文将简单介绍如何在 Mac 上安装 Homebrew 以及如何…

Android 15 适配之16K Page Size :为什么它会是最坑的一个适配点

首先什么是 Page Size &#xff1f;一般意义上&#xff0c;页面(Page)指的就是 Linux 虚拟内存管理中使用的最小数据单位&#xff0c;页面大小(Page Size)就是虚拟地址空间中的页面大小&#xff0c; Linux 中进程的虚拟地址空间是由固定大小的页面组成。 Page Size 对于虚拟内…

adblock:为AdGuard和uBlock Origin定制的个性化过滤规则

adblock&#xff1a;为AdGuard和uBlock Origin定制的个性化过滤规则 adblock Personal filters and rules for AdGuard/uBlock Origin 项目地址: https://gitcode.com/gh_mirrors/adb/adblock 项目介绍 adblock 项目是一个开源的过滤规则集合&#xff0c;专门为AdGuard…

Xcode16 iOS18 编译问题适配

问题1:ADClient编译报错问题 报错信息 Undefined symbols for architecture arm64:"_OBJC_CLASS_$_ADClient", referenced from:in ViewController.o ld: symbol(s) not found for architecture arm64 clang: error: linker command failed with exit code 1 (use …