被参数校验 / 日志逼疯?AOP:1 个切入点,所有方法自动加 buff

大家好,我是你们天天跟 BUG 斗智斗勇的后端博主~不知道你们有没有过这种经历:写了 10 个接口,每个接口都要加 "记录请求参数""统计耗时""打印返回值" 的代码,复制粘贴到第 5 个时,手都快按出火星子了 ------ 这时候要是有人告诉你,有个叫 AOP 的东西能帮你把这些重复活 "自动化",是不是瞬间想原地拜师?

今天咱就把 AOP 扒得明明白白,从 "它是啥" 到 "怎么用它写日志",全程无废话,还带实操代码,看完直接能上手!

一、先搞懂:AOP 到底是个啥 "偷懒工具"?

  • AOP:是Spring的2大核心(IoC和AOP)之一。
  • AOP:Aspect Oriented Programming,面向切面编程。是通过预编译方式(aspectj)或者运行期动态代理(Spring)实现程序功能的统一维护的技术。
  • AOP是OOP(Object Oriented Programming)的技术延续,是软件开发中的一个热点,也是Spring中的一个重要内容。利用AOP可以实现对业务逻辑各个部分之间的隔离,从而使得业务逻辑各部分之间的耦合性降低,提高程序的可重用性,同时提高了开发效率。

1. 一句话介绍:不碰业务代码,还能给它加功能

AOP(面向切面编程)本质是个 "后端特工"------ 比如你写了个用户登录接口(核心业务),想加 "记录登录日志""校验 token" 功能,不用在登录代码里改一行,AOP 能偷偷在接口执行前 / 后把这些活干了,主打一个 "润物细无声"。

2. 它的核心作用:专治 "重复代码绝症"

咱后端常见的 "重复病症":

  • 每个接口都要写日志(谁调用、传了啥、返回啥)
  • 每个方法都要加事务(开始 / 提交 / 回滚)
  • 多个接口要做权限校验(没登录就拦截)

AOP 能把这些 "重复操作" 抽成一个独立的 "切面",然后告诉它:"你去盯着所有 Controller 接口,执行前先校验权限,执行后记录日志"------ 从此告别复制粘贴,改需求时只改一个地方就行!

3. 本质 & 优势:动态代理是它的 "隐身衣"

AOP 底层靠动态代理实现(简单说就是给目标方法套个 "壳",壳里装增强逻辑),优势简直戳中后端痛点:

  • 作用:不修改源码的情况下,进行功能增强,通过动态代理实现的

    💡有2种常用的动态代理技术:

    • JDK的动态代理,基于接口实现代理
    • Cglib动态代理,基于继承实现代理,不要求有接口

    所以SpringBoot的AOP采用哪种动态代理呢?

    • SpringBoot从2.0开始,默认采用cglib动态
  • 优势:减少重复代码,提高开发效率,降低耦合度方便维护

  • 比如:给功能增加日志输出, 事务管理的功能

优势 大白话解释
解耦 业务代码(登录)和增强代码(日志)彻底分开,不用混在一起堆成 "屎山"
复用 一个切面能给 100 个方法用,不用写 100 遍重复代码
灵活 想给哪个方法加功能、想加啥功能,改个配置就行,不用动业务逻辑
少 bug 重复代码少了,复制粘贴时手抖少写个分号的概率也低了!

二、AOP 的 "黑话" 翻译:别被术语吓住!

刚学 AOP 时,总被 "切面""通知" 这些词搞懵,其实用 "餐厅服务" 类比一下,秒懂!

术语 黑话翻译 餐厅类比
切面(Aspect) 要做的 "增强任务集合" 服务员的 "工作清单"(端菜、收碗、找零)
切入点(Pointcut) 要增强的 "目标方法"(比如所有 Controller 接口) 服务员要服务的 "特定餐桌"(比如 1 号桌、VIP 区)
通知(Advice) 具体的 "增强动作"(比如方法执行前校验) 清单里的 "单个任务"(给 1 号桌端菜、收 3 号桌碗)
目标对象(Target) 被增强的 "原始业务对象"(比如 UserController) 被服务的 "顾客"
代理对象(Proxy) AOP 生成的 "带增强功能的对象" 穿了工作服、会按清单做事的 "服务员"
  • 目标对象(Target):要代理的/要增强的目标对象。
  • 代理对象(Proxy):目标对象被AOP织入增强后,就得到一个代理对象
  • 连接点(JoinPoint):能够被拦截到的点。在Spring里指的是方法,指能增强的方法
  • 切入点(PointCut) :要对哪些连接点进行拦截的定义。要增强的方法
  • 通知/增强(Advice) :拦截到连接点之后要做的事情。如何增强,额外添加上去的功能和能力
  • 切面(Aspect) :是切入点和通知的结合。 告诉Spring的AOP:要对哪个方法,做什么样的增强
  • 织入(Weaving):把增强/通知 应用到 目标对象来创建代理对象的过程

三、5 种通知类型:该在 "什么时候" 干活?

通知就是 AOP 的 "动作指令",关键是选对 "执行时机",比如日志要在方法执行后记,权限校验要在执行前做:

通知类型 执行时机 经典场景
前置通知(Before) 目标方法执行前 权限校验、参数合法性检查
后置通知(AfterReturning) 目标方法正常执行后 记录返回值、统计正常耗时
异常通知(AfterThrowing) 目标方法抛异常时 记录异常日志(比如保存报错信息到数据库)
最终通知(After) 目标方法无论成功 / 失败都执行 释放资源(比如关闭数据库连接)
环绕通知(Around) 目标方法执行前后都能插手 全流程控制(比如统计总耗时、手动控制方法是否执行)

划重点:环绕通知是 "全能选手",能拿到目标方法的入参、返回值,还能决定要不要执行目标方法,日志功能首选它!

四、实操:SpringBoot 里用 AOP,3 步搞定!

光说不练假把式,咱用 SpringBoot 搭个 AOP 环境,先跑通基础流程:

步骤 1:加依赖(Maven)

xml 复制代码
<!-- AOP核心依赖,SpringBoot已经封装好了 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

步骤 2:写切面类(核心!)

用@Aspect标注重庆,用@Component让 Spring 扫描到,再用通知注解写增强逻辑:

java 复制代码
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
// 1. 标为切面类 + 交给Spring管理
@Aspect
@Component
public class LogAspect {
    // 2. 定义切入点:要增强哪些方法?(这里先写个简单的,后面细讲表达式)
    @Pointcut("execution(* com.example.demo.controller.*.*(..))")
    public void logPointcut() {} // 这个方法只是个"标记",内容不用写
    // 3. 写通知:在切入点方法执行前做什么
    @Before("logPointcut()")
    public void doBefore() {
        System.out.println("前置通知:方法要执行啦,我先校验下权限~");
    }
    // 后置通知:方法正常执行后
    @AfterReturning("logPointcut()")
    public void doAfterReturning() {
        System.out.println("后置通知:方法执行完啦,我记录下返回值~");
    }
}

步骤 3:写个测试接口

less 复制代码
@RestController
@RequestMapping("/user")
public class UserController {
    @GetMapping("/login")
    public String login(String username) {
        return username + "登录成功!";
    }
}

启动项目,访问http://localhost:8080/user/login?username=zhangsan,控制台会打印:

复制代码
前置通知:方法要执行啦,我先校验下权限~
后置通知:方法执行完啦,我记录下返回值~

成了!这就是 AOP 的基本用法,不用改 Controller 一行代码,就加了增强功能~

五、切入点:怎么精准 "瞄准" 要增强的方法?

刚才的execution(* com.example.demo.controller..(..))就是切入点表达式,它是 AOP 的 "导航仪",决定了增强逻辑要加到哪个方法上。

1. 获取切入点的 2 种方式

咱后端常用的就这俩,注解式更灵活,XML 式老项目可能会用:

方式 写法 适用场景
注解式(推荐) 用@Pointcut+ 表达式,比如上面的例子 大多数 SpringBoot 项目,配置简单,一目了然
XML 式 在 applicationContext.xml 里配(现在很少用了) 老 Spring 项目,需要 XML 配置的场景

2. 切入点表达式:学会这 3 个,够用 90% 场景!

最常用的是execution表达式,格式记牢:

scss 复制代码
execution(访问修饰符 返回值 包名.类名.方法名(参数) 异常类型)

其中*代表 "任意",..代表 "任意参数 / 子包",举几个实战例子:

表达式 含义 实战场景
execution(* com.example.demo.controller..(..)) 匹配 controller 包下所有类的所有方法 给所有接口加日志
execution(* com.example.demo.service.UserService.*(String)) 匹配 UserService 里所有参数为 String 的方法 只增强用户相关的特定方法
execution(public * com.example.demo...(..)) 匹配 demo 包及其子包下所有 public 方法 给整个项目的 public 方法加权限校验

小技巧:如果想更灵活(比如给加了@Log注解的方法增强),可以用@annotation表达式:

java 复制代码
@Pointcut("@annotation(com.example.demo.annotation.Log)")
public void logPointcut() {}

这样只要在方法上加@Log,AOP 就会自动增强,比如:

less 复制代码
@Log // 加了这个注解,就会被上面的切入点匹配
@GetMapping("/login")
public String login(String username) { ... }

六、终极实战:用 AOP 实现 "全自动日志功能"

咱来写个实用的日志功能,需求是:记录每个接口的「请求 URL、请求方法、参数、返回值、耗时」,还要存到数据库(这里用打印日志代替,实际项目改存库就行)。

1. 明确 2 个关键问题

  • 增强谁:所有 Controller 接口(用execution表达式匹配)
  • 怎么增强:用环绕通知(因为要统计耗时,还要拿入参和返回值)

2. 完整代码实现

java 复制代码
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
@Aspect
@Component
public class LogAspect {
    // 切入点:匹配所有Controller接口
    @Pointcut("execution(* com.example.demo.controller.*.*(..))")
    public void logPointcut() {}
    // 环绕通知:核心逻辑
    @Around("logPointcut()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        // 1. 记录开始时间(统计耗时用)
        long startTime = System.currentTimeMillis();
        // 2. 获取请求信息(URL、请求方法)
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        String requestUrl = request.getRequestURL().toString(); // 请求URL
        String httpMethod = request.getMethod(); // 请求方法(GET/POST)
        // 3. 获取目标方法的参数和方法名
        Object[] args = joinPoint.getArgs(); // 方法参数
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod(); // 目标方法
        String methodName = method.getDeclaringClass().getName() + "." + method.getName(); // 类名.方法名
        // 4. 执行目标方法(就是原本的Controller方法)
        Object result = joinPoint.proceed(); // 这行必须有,否则目标方法不执行!
        // 5. 记录结束时间,计算耗时
        long endTime = System.currentTimeMillis();
        long costTime = endTime - startTime;
        // 6. 打印日志(实际项目里可以存到数据库,比如用MyBatis插入log表)
        System.out.println("===== 接口日志 =====");
        System.out.println("请求URL:" + requestUrl);
        System.out.println("请求方法:" + httpMethod);
        System.out.println("目标方法:" + methodName);
        System.out.println("请求参数:" + args[0]); // 这里简化,实际要遍历args
        System.out.println("返回值:" + result);
        System.out.println("耗时:" + costTime + "ms");
        System.out.println("===================");
        // 返回目标方法的返回值(否则接口调用者拿不到返回值)
        return result;
    }
}

3. 测试效果

访问http://localhost:8080/user/login?username=zhangsan,控制台会输出:

markdown 复制代码
===== 接口日志 =====
请求URL:http://localhost:8080/user/login
请求方法:GET
目标方法:com.example.demo.controller.UserController.login
请求参数:zhangsan
返回值:zhangsan登录成功!
耗时:5ms
===================

完美!以后不管加多少接口,都不用再写日志代码,AOP 会自动帮你记录 ------ 这就是 "一次编写,处处生效" 的快乐~

七、总结:AOP 到底帮我们解决了什么?

其实 AOP 的核心就是 "分离关注点":让业务代码只关心 "业务逻辑"(比如登录、下单),让增强代码只关心 "通用功能"(比如日志、事务)。

想象一下:没有 AOP 时,你写 100 个接口要加 100 遍日志代码,改日志格式时要改 100 个地方;有了 AOP,写 1 次切面,改 1 次配置,所有接口都能用上 ------ 这不就是后端开发者梦寐以求的 "偷懒自由" 吗?

最后留个小问题:你们在项目里用 AOP 踩过哪些坑?比如环绕通知忘了写joinPoint.proceed()导致方法不执行?欢迎在评论区分享,咱们一起避坑~

相关推荐
Ashlee_code1 小时前
香港券商櫃台系統跨境金融研究
java·python·科技·金融·架构·系统架构·区块链
还梦呦1 小时前
2025年09月计算机二级Java选择题每日一练——第五期
java·开发语言·计算机二级
2501_924890521 小时前
商超场景徘徊识别误报率↓79%!陌讯多模态时序融合算法落地优化
java·大数据·人工智能·深度学习·算法·目标检测·计算机视觉
bobz9652 小时前
复姓人口比例不到 0.11%
面试
從南走到北2 小时前
JAVA国际版东郊到家同城按摩服务美容美发私教到店服务系统源码支持Android+IOS+H5
android·java·开发语言·ios·微信·微信小程序·小程序
uhakadotcom3 小时前
什么是esp32?
面试·架构·github
毅航3 小时前
从原理到实践,讲透 MyBatis 内部池化思想的核心逻辑
后端·面试·mybatis
qianmoq3 小时前
第04章:数字流专题:IntStream让数学计算更简单
java
展信佳_daydayup3 小时前
02 基础篇-OpenHarmony 的编译工具
后端·面试·编译器
Always_Passion3 小时前
二、开发一个简单的MCP Server
后端