02 面向切面编程(AOP)核心概念:Aspect
- 一 定义
- 二 构成要素
- 1. Advice(通知)
- 2. Pointcut(切点)
- 3. Join Point(连接点)
- 三 织入(Weaving)
- 四 样例代码
- 1. 引入依赖
- 2. 新建Controller
- 3. 新建切面类(LoggingAspect)
- 五 测试截图
一 定义
Aspect
在编程中,尤其是 面向切面编程 (Aspect Oriented Programming
,简称 AOP
)中,是一个核心概念。它代表了一个跨越多个类和方法的关注点或功能的集合。这些关注点或功能通常与业务逻辑不直接相关,而是与系统的横切关注点有关,如日志记录、事务管理、权限验证等。通过使用 Aspect
,开发者可以将这些横切关注点从业务逻辑中分离出来,从而实现关注点与业务逻辑的解耦,提高代码的可维护性和重用性。
二 构成要素
一个完整的 Aspect
通常包含以下几个关键组成部分:
1. Advice(通知)
Advice
是 Aspect
中具体实施横切关注点行为的部分,即在特定的程序执行点(Join Point
)插入的额外操作。根据执行时机的不同,Advice
可分为以下几种类型:
类型 | 简述 |
---|---|
Before advice | 前置通知;在目标方法执行之前执行。 |
After returning advice | 返回后通知;在目标方法正常返回后执行。 |
After throwing advice | 抛出异常后通知;在目标方法抛出异常后执行。 |
After (finally) advice | 最终通知/最终回归通知;无论目标方法是否正常结束或抛出异常,都会在方法执行完毕后执行。 |
Around advice | 环绕通知;包围目标方法的执行,可以决定何时调用原方法、何时执行前置或后置动作,甚至完全阻止原方法的执行。 |
2. Pointcut(切点)
Pointcut
是一种表达式或模式,用于指定一组相关的Join Points
(程序执行过程中的特定位置,如方法调用、属性访问等),即定义了 Advice
应该在哪些特定的连接点上应用。切点表达式可以基于方法签名、类名、注解、包结构等多种条件进行匹配。
3. Join Point(连接点)
Join Point
是程序执行过程中一个明确的点,通常是方法调用、异常抛出、字段访问等事件的发生时刻。它是 Advice
可以介入并添加相应行为的地方。 Join Points
构成了程序执行流程中的所有可能干预点集合。
三 织入(Weaving)
Weaving
是将 Aspect
与应用程序的其他代码(包括主业务逻辑)进行融合以生成最终可执行程序的过程。Weaving
可以在不同的阶段进行:
阶段 | 简述 |
---|---|
编译时(Compile-time) | 通过特殊的编译器或插件,将 Aspect 转换为普通 Java 字节码。 |
类加载时(Load-time) | 使用特殊的类加载器,在加载类文件时动态地将 Aspect 逻辑合并到目标类中。 |
运行时(Runtime) | 通过代理机制(如 JDK 动态代理或 CGLIB 代理)在程序运行过程中动态地将 Aspect 逻辑织入目标对象。 |
四 样例代码
一个常见的使用 Aspect
的场景是日志记录。假设我们有一个业务方法,我们希望在方法执行前后记录日志。通过使用 Aspect
,我们可以创建一个日志记录的 Aspect
,然后在业务方法上应用这个 Aspect
,而无需修改业务方法的代码。
1. 引入依赖
<!-- Spring AOP: 提供AOP相关的注解。如:@Aspect、@Pointcut、@Before、@After、@AfterReturning、@AfterThrowing -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId><version>[版本号]</version>
</dependency>
2. 新建Controller
package com.demo.springboot3.controller;import com.demo.springboot3.entity.User;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;/*** @Description: // 用户信息:Controller* @Author: M.* @Date: 2024-04-25 20:11*/
@RestController
public class UserController {private Logger logger = LoggerFactory.getLogger(UserController.class);@PostMapping("/query")public String queryUser(@RequestBody User user) {logger.info("执行了业务方法!");return "success";}
}
3. 新建切面类(LoggingAspect)
package com.demo.springboot3.aspect;import com.demo.springboot3.controller.UserController;
import com.demo.springboot3.entity.User;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;/*** @Description: // 用户:切面* @Author: M.* @Date: 2024-04-28 10:11*/
@Aspect
@Component
public class LoggingAspect {private Logger logger = LoggerFactory.getLogger(UserController.class);@Pointcut("execution(* com.demo.springboot3.controller.UserController.queryUser(..))")public void pointCut() {}@Before("pointCut()")public void before(JoinPoint joinPoint) {logger.info("Entering method: " + joinPoint.getSignature());Object[] args = joinPoint.getArgs();User user = (User) args[0];logger.info("Parameter: " + user);// 此处执行一些记录日志,或者执行其他业务逻辑}@AfterReturning(pointcut = "pointCut()", returning = "result")public Object afterReturning(JoinPoint joinPoint, Object result) {logger.info("Entering method: " + joinPoint.getSignature() + " executed successfully and returned: " + result);// 此项执行一些记录日志,或者执行其他业务逻辑。如果业务逻辑无返回值,则不需要return,返回值类型修改为Voidreturn result;}@AfterThrowing(pointcut = "pointCut()", throwing = "e")public void afterThrowing(JoinPoint joinPoint, Exception e) {logger.info("Entering method: " + joinPoint.getSignature() + "; throw an exception: " + e.getMessage());// 此处执行一些记录异常信息到日志系统,或者执行其他异常处理逻辑}@Around("pointCut()")public Object around(ProceedingJoinPoint joinPoint) throws Throwable {long startTime = System.currentTimeMillis();// 在目标方法执行前打印日志logger.info("Entering method: " + joinPoint.getSignature());// 继续执行目标方法,并获取其返回值Object result = joinPoint.proceed();long endTime = System.currentTimeMillis();// 在目标方法执行后打印日志logger.info("Entering method: " + joinPoint.getSignature() + " - took " + (endTime - startTime) + "ms");return result;}@After("pointCut()")public void after(JoinPoint joinPoint) {logger.info("Entering method: " + joinPoint.getSignature() + " has finished execution.");// 此处执行一些清理工作,比如释放资源、记录方法执行结束日志等}
}
上述代码为案例代码,根据实际情况,自行搭配使用。
注解 | 简述 |
---|---|
@Aspect | 注解标识 LoggingAspect 类为一个切面类。 |
@Pointcut | 注解用于定义一个切入点表达式,以便识别目标方法。execution(* com.... 第一个 * 目标方法的返回值。com.demo.springboot3.controller.UserController.queryUser 是指定目标方法。execution(* com.....queryUser(..)) 中 .. 是目标方法的参数类型,可以用.. 代替。execution(* com.demo.....UserController.*(..)) 该路径下的所有方法。 |
@Before | 注解标识 before() 方法为一个前置通知。 |
@AfterReturning | 注解用于在目标方法成功返回后执行特定的操作。通常用于目标方法执行完毕后执行一些后续逻辑,比如记录日志、进行数据操作或者发送通知等。 |
@AfterThrowing | 注解用于定义一个异常通知,它会在目标方法抛出异常后执行。常用于处理或记录方法执行过程中出现的异常。 |
@Around | 此注解用于定义一个环绕通知,它会在目标方法执行前后都执行,并且可以控制目标方法的执行。环绕通知通常用于在方法执行前后添加额外的逻辑,并可以决定是否调用目标方法。 |
@After | 注解用于在目标方法执行完毕后(无论目标方法是否抛出异常),都会执行指定的通知方法。这种通知类型通常用于执行一些清理工作,比如释放资源、记录方法执行结束日志等。 |
JoinPoint | joinPoint.getSignature(): 获取通知的签名joinPoint.getSignature().getDeclaringTypeName(): 获取代理类的名字joinPoint.getSignature().getName(): 获取代理方法的名字joinPoint.getArgs(): 获取目标方法的参数列表信息(返回值是由参数构成的数组)。joinPoint.getThis(): AOP代理类的信息joinPoint.getTarget(): 代理的目标对象 |
五 测试截图
⚠️⚠️⚠️注意:
上述通知注解可以共存,关键在于如何合理设计切面逻辑,避免相互之间的干扰,并确保正确的执行流程。
本文隶属于 个人专栏
:00 个人小笔记📋📋📋
到这里 02 面向切面编程(AOP)核心概念:Aspect 就结束了!!!🎉🎉🎉
欢迎小伙伴们学习和指正!!!😊😊😊
祝大家学习和工作一切顺利!!!😎😎😎