Spring AOP 入门、实现原理及实践案例

news/2024/11/22 17:46:20/

  Spring 是目前 Java 领域最为流行的开发框架之一,它提供了很多方便快捷的功能,其中之一就是 AOP(Aspect Oriented Programming),即面向切面编程。本文将详细介绍 Spring AOP 的实现原理、核心概念以及在实际应用中的使用案例。

1. AOP 的基本概念

  1.1 切入点(Pointcut)

  切入点是一个表达式,用于描述哪些方法将被拦截执行,通常使用正则表达式或通配符来匹配方法名或类名。Spring AOP 支持两种类型的切入点:静态切入点和动态切入点。静态切入点在创建时就已经确定,而动态切入点则需要在运行时根据实际情况进行计算。

  1.2 通知(Advice)

  通知是指 AOP 在拦截到被选定的方法后,所执行的代码块。Spring AOP 中支持五种不同类型的通知:

  • 前置通知(Before Advice):在目标方法执行之前执行。
  • 后置通知(After Returning Advice):在目标方法正常完成后执行。
  • 异常通知(After Throwing Advice):在目标方法抛出异常时执行。
  • 最终通知(After Finally Advice):在目标方法完成之后执行,无论是否发生异常。
  • 环绕通知(Around Advice):覆盖目标方法的执行,需要手动调用目标方法。

  1.3 切面(Aspect)

  切面是将切入点和通知组合在一起的实体对象,用于描述哪些方法应该在何时被拦截,并指定要执行的通知代码块。

2. Spring AOP 的实现原理

  Spring AOP 的实现原理是基于 JDK 动态代理或 CGLIB 字节码技术。在目标对象上创建一个动态代理或子类,拦截所需要的方法并执行对应的通知,在执行完毕后再将控制权转交给目标对象。这样就可以在不修改目标对象代码的情况下,实现对目标方法的增强。

  具体来说,Spring AOP 拦截方法的实现分为以下几个步骤:

  1. 定义切面,配置切入点和通知。
  2. 根据目标对象类型创建代理对象。
  3. 拦截符合切入点要求的方法,并执行所配置的通知代码块。

3. Spring AOP 实践案例

  为了更好地理解 Spring AOP 的实现原理,下面以一个简单的实践案例为例:使用 AOP 打印每个方法执行的日志,日志内容包括方法执行、执行结果、异常信息等方面。

  3.1 定义切入点和通知

  首先,需要定义一个切入点,这里为了方便,直接匹配所有 public 方法:

@Pointcut("execution(public * *(..))")
public void logPointCut() {}

  然后,定义一个环绕通知,在目标方法执行前后打印日志:

@Around("logPointCut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {long startTime = System.currentTimeMillis();Object result = joinPoint.proceed();long endTime = System.currentTimeMillis();long duration = endTime - startTime;String methodName = joinPoint.getSignature().getName();String className = joinPoint.getTarget().getClass().getSimpleName();String args = Arrays.toString(joinPoint.getArgs());if (result instanceof Serializable) {log.info("{}#{}({}, {}) : {} , 耗时 {} ms", className, methodName, args, result, duration);} else {log.info("{}#{}({}, [not serializable object]) , 耗时 {} ms", className, methodName, args, duration);}return result;
}

  上述代码中,@Around 注解表示该方法是一个环绕通知,并且会拦截名为 logPointCut() 的切入点所匹配到的所有方法。ProceedingJoinPoint 类型表示需要拦截的连接点,通过调用其 proceed() 方法可以继续执行原本要执行的目标方法,返回值类型为 Object,即目标方法的执行结果。

  3.2 配置 AOP

  接下来需要在 Spring 配置文件中配置 AOP,将切面和切入点以及通知绑定在一起:

<!-- 定义切面 -->
<bean id="logAspect" class="com.example.LogAspect"/><!-- 配置 AOP -->
<aop:config><aop:aspect ref="logAspect"><!-- 声明切入点 --><aop:pointcut id="logPointCut" expression="execution(public * *(..))"/><!-- 声明通知 --><aop:around method="around" pointcut-ref="logPointCut"/></aop:aspect>
</aop:config>

  上述代码中,<bean> 标签定义了一个名为 logAspect 的 JavaBean 对象,并指定其类为 com.example.LogAspect,即定义了切面对象。而 <aop:config> 标签则表示开始 AOP 配置,其中 <aop:aspect> 定义了一个切面,其 ref 属性指向 logAspect,即引用前面定义的切面对象;<aop:pointcut> 声明了一个切入点,其 expression 属性使用与前面定义的一致;<aop:around> 则声明了一个环绕通知,将其方法名指定为 around,并将其与前面定义的切入点绑定在一起。

  3.4 SpringBoot版本的AOP案例

注意:在 Spring Boot 中使用 AOP 需要在启动类的配置中添加 @EnableAspectJAutoProxy 注解来开启自动代理功能,以便能够使用 AOP 切面。

@Aspect
@Component
public class LogAspect {@Pointcut("execution(public * *(..))")public void logPointCut() {}@Around("logPointCut()")public Object around(ProceedingJoinPoint joinPoint) throws Throwable {long startTime = System.currentTimeMillis();Object result = joinPoint.proceed();long endTime = System.currentTimeMillis();long duration = endTime - startTime;String methodName = joinPoint.getSignature().getName();String className = joinPoint.getTarget().getClass().getSimpleName();String args = Arrays.toString(joinPoint.getArgs());if (result instanceof Serializable) {log.info("{}#{}({}, {}) : {} , 耗时 {} ms", className, methodName, args, result, duration);} else {log.info("{}#{}({}, [not serializable object]) , 耗时 {} ms", className, methodName, args, duration);}return result;}
}

  3.5 测试应用

  最后,在业务代码中添加一些方法并测试:

@Service
public class MyService {public void sayHello(String name) {System.out.println("Hello, " + name + "!");}public int divide(int a, int b) {return a / b;}
}

  上述代码定义了一个名为 MyService 的服务类,其中包含了两个简单的方法,分别是打印问候语和除法运算。现在,只需要在 Spring 容器中注入该服务类,并调用其方法即可,AOP 将会在控制台打印出对应的日志信息:

MyService myService = context.getBean(MyService.class);
myService.sayHello("World");
myService.divide(10, 2);
myService.divide(10, 0);

  其中第一行代码是从 Spring 容器中获取该服务类的实例对象,然后依次调用 sayHellodivide 方法,可以看到控制台输出了类似下面的日志信息:

com.example.MyService#sayHello([World], null) : null , 耗时 0 ms
com.example.MyService#divide([10, 2], 5) : 2 , 耗时 0 ms
com.example.MyService#divide([10, 0], java.lang.ArithmeticException: / by zero) , 耗时 1 ms

4. 总结

  Spring AOP 是一种很方便实用的面向切面编程技术,通过代理或字节码技术实现对目标方法的拦截和通知,从而实现对目标方法的增强。在实际应用中,通常使用切入点、通知和切面来描述 AOP 的核心概念,并通过配置文件将它们组合在一起来实现具体功能。


http://www.ppmy.cn/news/89421.html

相关文章

算法Day17 | 110.平衡二叉树, 257. 二叉树的所有路径,404.左叶子之和

Day17 110.平衡二叉树257. 二叉树的所有路径404.左叶子之和 110.平衡二叉树 题目链接&#xff1a;110.平衡二叉树 求高度&#xff0c;后序遍历。 递归 后序遍历 如果某一节点为非平衡二叉树&#xff0c;则整个二叉树就为非平衡二叉树&#xff0c;可以一直向上层递归-1 class…

从零开始搭建高效的文件服务器:FastDFS与Nginx完美结合,内网穿透实现公网访问

目录 前言 1. 本地搭建FastDFS文件系统 1.1 环境安装 1.2 安装libfastcommon 1.3 安装FastDFS 1.4 配置Tracker 1.5 配置Storage 1.6 测试上传下载 1.7 与Nginx整合 1.8 安装Nginx 1.9 配置Nginx 2. 局域网测试访问FastDFS 3. 安装cpolar内网穿透 4. 配置公网访问…

ChatGPT之公文写作

公务文章主要适用于政府部门、机关、事业单位以及其他公共组织的文件、公告、通知等文稿。 根据《党政机关公文处理工作条例》&#xff0c;公文种类主要有15种。按照行文流向&#xff0c;可以分为上行文、平行文、下行文。 1、上行文&#xff1a;请示、报告、意见。 2、平行…

分享去年学习github命令行操作的笔记

git branch -M main 给远程分支改名 一、本地库操作 1.创建本地目录&#xff0c;用于存储要上传的文本文件。可以手动创建也可以用带命令行 mkdir <文件名> 2.进入文件夹cd <文件名> 3第一次创建时需要初始化仓库git init mac显示隐藏文件SHIFTCOMMAND. mac…

如何在宝塔面板后的阿里云服务器运行Flask项目并公网可以访问?

在你的服务器安装宝塔面板 宝塔面板是服务器运维管理系统 使用宝塔前&#xff1a; 手工输入命令安装各类软件&#xff0c;操作起来费时费力并且容易出错&#xff0c;而且需要记住很多Linux的命令&#xff0c;非常复杂。 使用宝塔后&#xff1a; 2分钟装好面板&#xff0c;一键…

可分析表情和情绪的轻量化眼镜:Emteq OCOsense解析

近年来&#xff0c;越来越多VR头显开始尝试结合眼球追踪、手势追踪等生物识别技术&#xff0c;甚至在一些VR社交场景&#xff0c;也在探索将Avatar与面部识别功能结合。可以想象&#xff0c;未来生物识别与AR/VR等穿戴技术的关系将越来越紧密&#xff0c;尽管现阶段相关硬件在体…

4EVERLAND 与 Sei 合作:用去中心化存储赋能最快的 L1

我们很高兴地宣布我们与 Sei 的合作&#xff0c;Sei 是最快的第 1 层交易。 该合作伙伴关系旨在优化架构&#xff0c;在现有最快的区块链上为所有类型的去中心化应用程序提供最佳的去中心化存储功能。 4EVERLAND&#xff0c;Sei的一站式去中心化存储基础设施 4EVERLAND是一个…

WSL 双系统端口映射,网络穿透最新教程

目录 1 进入wsl 1.1 进入root模式 1.2 随便安装个东西 2 打开win的PowerShell 2.1 查看虚拟机的ip地址 2.2 端口映射转发 2.3 验证是否成功 2.4 删除映射端口命令 1 进入wsl 这里使用的是ubuntuLiunx操作系统 打开wsl&#xff0c;搜索即可。 1.1 进入root模式 命令 …