Spring AOP、日志切面与声明式事务原理

AOP 是 Spring 面试里的高频点,但很多回答只停留在"面向切面编程"。这句话没错,但不够。面试官更想听到的是:你有没有在项目里用过 AOP,它解决了什么重复问题,Spring 事务为什么也依赖 AOP。

一句话概括:AOP 就是把日志、事务、缓存、权限这类横切逻辑从业务代码里抽出来,通过代理在目标方法前后织入增强逻辑。

AOP 解决什么问题

业务代码里经常会出现一类逻辑:它们和具体业务无关,但很多方法都需要。

比如:

横切逻辑 典型用途
操作日志 记录谁在什么时候调用了哪个功能
事务处理 方法执行前开启事务,成功提交,失败回滚
缓存处理 方法执行前查缓存,执行后写缓存
权限校验 方法执行前检查当前用户是否有权限

如果每个业务方法都手写一遍,就会出现大量重复代码。AOP 的价值就是把这些公共逻辑抽成切面。


成功
异常
请求进入

Controller
AOP 代理对象
环绕通知

前置逻辑
调用

joinPoint.proceed
执行业务方法
执行结果
记录成功日志

和耗时
记录失败日志

并继续抛出

项目里怎么用 AOP 记录日志

操作日志是最容易落地的 AOP 场景。

一个后台系统中,新增用户、更新用户、删除用户这些动作通常都要记录:

  • 当前登录用户是谁。
  • 请求方式是什么。
  • 访问地址是什么。
  • 操作模块是什么。
  • 登录 IP 是多少。
  • 操作时间是什么。
  • 执行结果是成功还是失败。

这些信息和"新增用户"本身没有强绑定,却又分散在很多接口里。适合用切面统一处理。

java 复制代码
@Aspect
@Component
public class OperationLogAspect {

    @Pointcut("@annotation(com.example.LogOperation)")
    public void logPointcut() {
    }

    @Around("logPointcut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.currentTimeMillis();
        try {
            Object result = joinPoint.proceed();
            saveLog(joinPoint, true, System.currentTimeMillis() - start);
            return result;
        } catch (Throwable ex) {
            saveLog(joinPoint, false, System.currentTimeMillis() - start);
            throw ex;
        }
    }

    private void saveLog(ProceedingJoinPoint joinPoint, boolean success, long cost) {
        // 获取用户名、请求方式、访问地址、模块名称、IP、执行耗时,然后写入日志表
    }
}

这里的核心是 @Around 环绕通知。它包住了目标方法:

  1. 方法执行前可以拿请求信息。
  2. 调用 joinPoint.proceed() 执行业务方法。
  3. 方法执行后可以拿返回结果和耗时。
  4. 方法抛异常时可以记录失败日志。

Spring 事务为什么也是 AOP

声明式事务的核心注解是 @Transactional

java 复制代码
@Transactional(rollbackFor = Exception.class)
public void saveUser(User user) {
    userMapper.insert(user);
    roleMapper.insertDefaultRole(user.getId());
}

这段业务代码里没有手写开启事务、提交事务、回滚事务,但事务确实生效了。原因是 Spring 会通过 AOP 代理在方法前后织入事务逻辑。




调用带

Transactional 的方法
进入事务代理
开启事务

绑定数据库连接
执行目标

业务方法
是否抛出

符合规则的异常
提交事务
回滚事务
返回结果
继续抛出异常

可以把声明式事务理解成下面这种环绕通知:

java 复制代码
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
    try {
        // 开启事务
        Object result = joinPoint.proceed();
        // 提交事务
        return result;
    } catch (Throwable ex) {
        // 回滚事务
        throw ex;
    }
}

真实的 Spring 事务比这个复杂得多,还涉及事务管理器、连接绑定、传播行为、隔离级别等。但主线就是:方法执行前开启事务,执行成功提交,执行失败回滚。

编程式事务和声明式事务

Spring 支持两种事务管理方式:

方式 写法 特点
编程式事务 TransactionTemplate 需要在业务代码里显式写事务逻辑,有侵入性
声明式事务 @Transactional 基于 AOP,业务代码更干净,项目中最常用

编程式事务不是不能用,而是适合特别细粒度、特别明确的事务边界。普通业务方法优先用声明式事务。

AOP 相关注解怎么记

注解 作用
@Aspect 声明当前类是切面
@Pointcut 定义切点,说明哪些方法会被增强
@Before 目标方法执行前增强
@After 目标方法执行后增强,不区分成功失败
@AfterReturning 目标方法正常返回后增强
@AfterThrowing 目标方法抛异常后增强
@Around 环绕增强,可以控制目标方法执行前后逻辑

真实项目里,日志和事务最常用的是环绕通知,因为它能同时覆盖方法执行前、执行后、异常三个阶段。

面试回答模板

可以这样回答:

AOP 是面向切面编程,主要用于抽取和业务无关但很多地方都会用到的公共逻辑,比如操作日志、事务、缓存、权限校验。Spring AOP 底层通过代理对象对目标方法进行增强,在方法执行前后织入切面逻辑。项目中我们可以用 @Aspect 定义日志切面,用切点表达式匹配需要记录日志的方法,用 @Around 环绕通知获取请求参数、用户信息、IP、执行结果和耗时,最后写入日志表。

事务可以这样补充:

Spring 声明式事务也是基于 AOP 实现的。@Transactional 标注的方法会被事务代理增强,方法执行前开启事务,方法正常执行完成后提交事务,抛出符合回滚规则的异常时回滚事务。

小结

AOP 不要只背概念,要抓住落地场景。

日志切面能证明你真的用过 AOP;声明式事务能证明你理解 Spring 底层为什么需要 AOP。二者放在一起理解,AOP 就不再是抽象术语,而是一套解决横切逻辑复用的工程手段。

相关推荐
zhangxingchao3 小时前
AI应用开发六:企业知识库
前端·人工智能·后端
No8g攻城狮3 小时前
【人大金仓】wsl2+ubuntu22.04安装人大金仓数据库V9
java·数据库·spring boot·非关系型数据库
xiaoerbuyu12333 小时前
开源Java 邮箱 基于SpringBoot+Vue前后端分离的电子邮件
java·开发语言
红尘散仙3 小时前
一个 `#[uniffi::export]`,把 Rust 接进 React Native
前端·后端·rust
红尘散仙3 小时前
一行 `#[specta::specta]`,让 Tauri IPC 有类型
前端·后端·rust
C+++Python4 小时前
C++ 进阶学习完整指南
java·c++·学习
zhangjw344 小时前
第11篇:Java Map集合详解,HashMap底层原理、哈希冲突、JDK1.8优化、遍历方式彻底吃透
java·开发语言·哈希算法
还得是你大哥4 小时前
Java互联网医院管理系统源码SpringBoot
java·spring boot·vue