SpringBoot Aop 详解

什么是aop

AOP(面向切面编程)

AOP是一种编程范式,它通过将横切关注点(如日志、安全、事务等)从主要业务逻辑中分离,降低模块间的耦合度,提升代码的可重用性和可维护性。AOP可以看作是对传统面向对象编程(OOP)的补充,尤其是在处理那些跨多个类的公共逻辑时。

使用场景

AOP常用于以下场景:

  • 日志记录:集中管理日志的记录逻辑。
  • 性能监控:统一监控方法的执行时间。
  • 安全控制:在方法执行前进行权限检查。
  • 事务管理:管理数据库事务的开始和结束。
  • 异常处理:集中处理异常逻辑。
  • 资源管理:如连接池的管理。

为什么需要AOP

在OOP中,公共功能的实现往往导致代码重复和维护困难。通过AOP,开发者可以将这些横切关注点提取到单独的切面中,从而避免重复代码,提高代码的整洁性和可维护性。例如,日志代码可以集中在一个切面中,而不是散布在每个业务方法中,这样在需要修改日志逻辑时,只需在一个地方进行更新。

技术要点

通知(Advice)

  • 通知定义了“什么时候”和“做什么”。可以理解为在某个连接点执行的具体逻辑。通知类型包括:
    • 前置通知(Before):在目标方法执行之前运行。
    • 后置通知(After):在目标方法执行之后运行。
    • 环绕通知(Around):在目标方法执行前后都能运行,允许控制方法的执行。
    • 异常通知(After Throwing):在目标方法抛出异常时执行。
    • 返回通知(After Returning):在目标方法正常返回后执行。

连接点(Join Point)

  • 连接点是程序执行过程中的一个特定位置,任何可以被通知拦截的点。例如,方法调用、对象创建等都是连接点。

切点(Pointcut)

  • 切点是定义在哪些连接点上应用通知的表达式。切点用于过滤连接点,指定哪些方法或类会受到通知的影响。

切面(Aspect)

  • 切面是通知和切点的结合,封装了横切关注点的逻辑。它定义了在哪些连接点上执行哪些通知,形成完整的逻辑。

引入(Introduction)

  • 引入允许在不修改现有类的情况下,向其添加新方法或属性。这使得现有类可以具备额外的功能,增加了灵活性。

织入(Weaving)

  • 织入是将切面应用到目标对象的过程。织入可以发生在不同的时间点:
    • 编译期织入:在编译阶段将切面集成到目标类中。
    • 类加载期织入:在类被加载到JVM时进行织入。
    • 运行期织入:在程序运行时进行织入,通常使用动态代理实现。

整合使用

导入依赖

在springboot中使用aop要导aop依赖

        <!--aop 切面-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

创建服务类

创建一个服务类,定义业务逻辑:

package com.example.demo.Service;

import org.springframework.stereotype.Service;

@Service
public class UserService {
    public String createUser() {
        return "用户创建成功";
    }
}

创建切面类

使用AOP定义切面,添加通知:

package com.example.demo.Aspect;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.After;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class LoggingAspect {

    @Before("execution(* com.example.demo.controller.*.*(..))")
    public void logBefore() {
        System.out.println("目标方法即将被调用");
    }

    @After("execution(* com.example.demo.controller.*.*(..))")
    public void logAfter() {
        System.out.println("目标方法已执行");
    }
}

创建Controller

创建一个Controller,通过HTTP请求调用服务:

package com.example.demo.controller;

import com.example.demo.Service.UserService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/users")
public class UserController {

    private final UserService userService;

    public UserController(UserService userService) {
        this.userService = userService;
    }

    @GetMapping("/create")
    public String createUser() {
        return userService.createUser();
    }
}

运行DemoApplication,在Postman访问http://localhost:8080/users/create,

不出意外,控制台输出如图所示: 

定义通知

前置通知(@Before):在目标方法调用之前执行。适用于执行前的准备工作,如日志记录、权限检查等。

@Before("execution(* com.example.service.*.*(..))")
public void logBefore() {
    System.out.println("目标方法即将被调用");
}

后置通知(@After):在目标方法完成之后执行,不论目标方法是否成功。通常用于清理工作或收尾日志。

@After("execution(* com.example.service.*.*(..))")
public void logAfter() {
    System.out.println("目标方法已执行");
}

环绕通知(@Around):在目标方法调用前后执行,允许控制方法的执行,可以选择是否调用目标方法。

@Around("execution(* com.example.service.*.*(..))")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
    System.out.println("目标方法执行前");
    Object result = joinPoint.proceed(); // 调用目标方法
    System.out.println("目标方法执行后");
    return result;
}

返回通知(@AfterReturning):在目标方法成功执行之后执行。可用于获取方法返回值,进行进一步处理。

@AfterReturning(pointcut = "execution(* com.example.service.*.*(..))", returning = "result")
public void logAfterReturning(Object result) {
    System.out.println("目标方法成功返回,结果为: " + result);
}

异常通知(@AfterThrowing):在目标方法抛出异常后执行。可用于处理异常情况,记录错误信息。

@AfterThrowing(pointcut = "execution(* com.example.service.*.*(..))", throwing = "ex")
public void logAfterThrowing(Exception ex) {
    System.out.println("目标方法抛出异常: " + ex.getMessage());
}

Spring Boot AOP 实践案例

下面是一个使用 Spring Boot AOP 的简单实践案例,我们将使用 AOP 来记录方法的执行时间。

创建服务类

创建一个服务类,包含要监控的方法:

package com.example.demo.Service;

import org.springframework.stereotype.Service;

/**
 * 用户服务类
 * 提供用户创建和删除的功能
 */
@Service
public class UserService {

    /**
     * 创建用户
     *
     * @return 创建成功的信息
     * @throws InterruptedException 如果模拟执行时间过程中被中断
     */
    public String createUser() throws InterruptedException {
        // 模拟方法执行时间
        Thread.sleep(1000);
        return "用户创建成功";
    }

    /**
     * 删除用户
     *
     * @return 删除成功的信息
     * @throws InterruptedException 如果模拟执行时间过程中被中断
     */
    public String deleteUser() throws InterruptedException {
        // 模拟方法执行时间
        Thread.sleep(500);
        return "用户删除成功";
    }
}

创建切面类

定义切面,用于记录方法执行时间:

package com.example.demo.Aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

/**
 * 用于记录服务层方法执行时间的切面类
 */
@Aspect
@Component
public class PerformanceLoggingAspect {

    /**
     * 记录目标方法的执行时间
     * 
     * @param joinPoint 切入点对象,包含了目标方法的信息
     * @return 目标方法的执行结果
     * @throws Throwable 如果目标方法执行过程中抛出异常,则抛出Throwable
     */
    @Around("execution(* com.example.demo.Service.*.*(..))")
    public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
        // 记录开始时间
        long start = System.currentTimeMillis();

        // 执行目标方法
        Object proceed = joinPoint.proceed();

        // 计算方法执行时间
        long executionTime = System.currentTimeMillis() - start;

        // 打印方法执行时间
        System.out.println(joinPoint.getSignature() + " 耗时: " + executionTime + "毫秒");

        return proceed;
    }
}
package com.example.demo.Aspect;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.After;
import org.springframework.stereotype.Component;

/**
 * 切面类,用于在方法执行前后记录日志
 */
@Aspect
@Component
public class LoggingAspect {

    /**
     * 在目标方法执行之前记录日志
     */
    @Before("execution(* com.example.demo.controller.*.*(..))")
    public void logBefore() {
        System.out.println("目标方法即将被调用");
    }

    /**
     * 在目标方法执行之后记录日志
     */
    @After("execution(* com.example.demo.controller.*.*(..))")
    public void logAfter() {
        System.out.println("目标方法已执行");
    }
}

创建Controller

创建一个Controller来调用服务方法:

package com.example.demo.controller;

import com.example.demo.Service.UserService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 用户控制器类,处理与用户相关的HTTP请求
 */
@RestController
@RequestMapping("/users")
public class UserController {

    // 用户服务实例,用于处理用户相关的业务逻辑
    private final UserService userService;

    /**
     * UserController构造函数
     * @param userService 用户服务实例
     */
    public UserController(UserService userService) {
        this.userService = userService;
    }

    /**
     * 创建用户
     *
     * @return 创建用户的结果
     * @throws InterruptedException 如果用户创建过程中被中断
     */
    @GetMapping("/create")
    public String createUser() throws InterruptedException {
        return userService.createUser();
    }

    /**
     * 删除用户
     *
     * @return 删除用户的结果
     * @throws InterruptedException 如果用户删除过程中被中断
     */
    @GetMapping("/delete")
    public String deleteUser() throws InterruptedException {
        return userService.deleteUser();
    }
}

通过浏览器或Postman访问:

创建用户:

http://localhost:8080/users/create

删除用户:

http://localhost:8080/users/delete

控制台会输出以下内容: 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Onlooker﹒

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值