Spring AOP 入门:从 AOP 概念到第一个耗时统计切面

目录

​编辑

[1. 痛点推导:我们为什么需要 AOP?](#1. 痛点推导:我们为什么需要 AOP?)

[2. AOP 与拦截器、全局异常处理的关系](#2. AOP 与拦截器、全局异常处理的关系)

[3. 方案落地:快速入门 AOP](#3. 方案落地:快速入门 AOP)

[4. 流程变化:加入 AOP 后发生了什么?](#4. 流程变化:加入 AOP 后发生了什么?)


1. 痛点推导:我们为什么需要 AOP?

假设我们现在有一个图书管理系统,里面有很多业务接口(如查询图书、新增图书等)。

某天,客户抱怨:"系统最近变慢了,接口耗时太长,需要优化!"

第一步: 我们得先找出到底哪个方法慢,这就需要统计所有业务方法的执行耗时。(这里没有是使用jmeter来进行,只是进行一个场景的模拟)

传统方案:

我们只能在每个业务方法的首尾加上时间记录代码:

java 复制代码
public void function1() {
    long startTime = System.currentTimeMillis(); // 记录开始时间
    test1(); // 真正的业务逻辑
    long endTime = System.currentTimeMillis(); // 记录结束时间
    log.info("function1执行耗时: " + (endTime - startTime) + " ms");
}

方案推演出的死局:

一个项目中几十上百个接口,每个接口又有多个方法。如果每个方法都这么写:

  1. 代码严重冗余:时间统计的代码比业务代码还多。

  2. 侵入性极强:业务代码被非业务代码(日志打印)严重污染。

  3. 难以维护:哪一天不统计耗时了,或者统计逻辑变了,又要挨个方法去改。

思考: 有没有一种技术,能在不修改原始业务方法 的前提下,把"统计耗时"这个动作像包包子一样,自动包在原方法的外面?

这就是 AOP(Aspect Oriented Programming,面向切面编程) 诞生的意义。AOP 的本质,就是对某一类特定问题(如日志、事务、耗时统计)的集中处理,达到无侵入的解耦。

2. AOP 与拦截器、全局异常处理的关系

你可能用过拦截器(Interceptor)或 @ControllerAdvice。

  • 拦截器 的维度是 URL(一次 HTTP 请求),它能处理登录校验,但它管不到普通类里的普通方法。

  • AOP 的维度更加细致 ,它可以精准定位到包、类、方法名、参数 级别。

    简单来说:拦截器、全局异常处理,都是 AOP 思想在 Web 层面的一种具体实现。而 Spring AOP 则提供了更强大的底层支持。

  • 简单来说:拦截器、统一异常处理、统一返回格式,都是 AOP 思想在 Web 层面的一种具体实现;而 Spring AOP 提供了更底层、更通用的支持。

    技术 作用维度 典型场景
    拦截器 URL 请求 登录校验、请求日志
    @ControllerAdvice 全局 Controller 层 全局异常处理、数据绑定
    Spring AOP 包、类、方法、参数 方法耗时、事务、权限、日志

3. 方案落地:快速入门 AOP

需求: 统计 com.example.demo.controller 包下所有类、所有方法的执行时间。

第一步:引入依赖

XML 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

第二步:编写切面类

java 复制代码
@Slf4j
@Aspect // 1. 告诉 Spring:这是一个切面类
@Component
public class TimeAspect {
    
    // 2. @Around(环绕通知):表示在目标方法的前后都执行。括号里是切点表达式,定义了拦截哪些方法。
    @Around("execution(* com.example.demo.controller.*.*(..))") 
    public Object recordTime(ProceedingJoinPoint pjp) throws Throwable {
        // 【前置逻辑】
        long begin = System.currentTimeMillis();
        
        // 3. 调用 ProceedingJoinPoint.proceed() 执行原始方法
        Object result = pjp.proceed(); 
        
        // 【后置逻辑】
        long end = System.currentTimeMillis();
        log.info(pjp.getSignature() + "执行耗时: {}ms", end - begin);
        
        return result; // 必须返回原始方法的执行结果
    }
}

4. 流程变化:加入 AOP 后发生了什么?

在没有 AOP 之前,调用方(比如前端)请求 BookController 的 queryBookById 方法,是直接调用 目标对象。

加入 AOP 后,流程变成了:

  1. 调用方发起调用。

  2. 请求被 Spring AOP 拦截。

  3. 先执行我们写的前置逻辑(记录 begin 时间)。

  4. 通过 proceed() 方法放行 ,此时才真正执行 queryBookById 的原始业务代码。

  5. 原始方法执行完毕,返回结果。

  6. 接着执行我们写的后置逻辑(记录 end 时间并打印日志)。

  7. 最后,把原始方法的返回值返回给调用方。

AOP 调用时序图

由此推导出了 AOP 的核心优势:

  • 代码无侵入:原始方法一行没改。

  • 减少重复代码:只写一次切面即可。

  • 维护方便:业务方法和增强逻辑完全解耦。

相关推荐
系统集成-鹰翔29 分钟前
ERP单据已作废为什么商品仍不能删除,引用检查与软删除怎么查
javascript·经验分享·后端
天天被压力31 分钟前
【A股分钟级高频数据完全手册 #09】A股历史分时交易数据接口:普通分时怎么批量下
java·人工智能·python
Moment32 分钟前
Harness Engineering 为什么是 AI Coding 的工程底座 ❓❓❓
前端·后端·面试
天天被压力39 分钟前
【A股分钟级高频数据完全手册 #10】股票实时行情数据接口Python:公开源实时交易取数
java·人工智能·python
apcipot_rain1 小时前
C++转Java日记(05)
java·开发语言·c++
mldong1 小时前
主办先挂起,收齐意见再拍板:jeeflow 的动态会签
后端·架构
CoderYanger6 小时前
Java EE 进阶:8.MyBatis 操作数据库(入门)(第二弹)
java·数据库·spring boot·spring·面试·java-ee·mybatis
CQU_JIAKE9 小时前
11.7[高体】
java·后端·spring
Csvn9 小时前
认识 Nginx
后端·nginx