02 面向切面编程(AOP)核心概念:Aspect

发布于:2024-04-28 ⋅ 阅读:(22) ⋅ 点赞:(0)


在这里插入图片描述

一 定义

Aspect 在编程中,尤其是 面向切面编程Aspect Oriented Programming,简称 AOP )中,是一个核心概念。它代表了一个跨越多个类和方法的关注点或功能的集合。这些关注点或功能通常与业务逻辑不直接相关,而是与系统的横切关注点有关,如日志记录、事务管理、权限验证等。通过使用 Aspect ,开发者可以将这些横切关注点从业务逻辑中分离出来,从而实现关注点与业务逻辑的解耦,提高代码的可维护性和重用性。

二 构成要素

一个完整的 Aspect 通常包含以下几个关键组成部分:

1. Advice(通知)

AdviceAspect 中具体实施横切关注点行为的部分,即在特定的程序执行点(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,返回值类型修改为Void
        return 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 就结束了!!!🎉🎉🎉
欢迎小伙伴们学习和指正!!!😊😊😊
祝大家学习和工作一切顺利!!!😎😎😎


网站公告

今日签到

点亮在社区的每一天
去签到