切面学习笔记

SpringAOP 切面学习笔记

一、AOP 基础概念

1. 核心名词
名词 释义 本项目对应
切面 Aspect 封装统一横切逻辑的类,@Aspect+@Component ExportLimitAspect、SqlBase64Aspect
切点 Pointcut 匹配哪些类 / 方法需要增强,execution 表达式 execution(* ...exportFile(..))
通知 Advice 切点匹配后执行的增强逻辑 @Around 环绕通知(最常用)、@Before、@After
连接点 JoinPoint 被拦截的原始方法 exportFile()、findDataCount()
目标对象 被代理的类(Jar 包内 Controller) Query_datasetController

横切:非业务主干、通用逻辑:参数预处理、权限、导出条数校验、加解密、日志、限流。

二、切面开发标准步骤(实操流程)

创建切面类,注册 SpringBean
  1. @Component:交给 SpringIOC 容器管理(切面本质也是 Bean,必须被扫描实例化
  2. @Aspect:标识当前类为 AOP 切面类
java 复制代码
@Aspect
@Component
public class ExportLimitAspect{}
定义切点表达式 execution

格式:execution(返回值 全类名.方法名(参数))

java 复制代码
// 匹配Jar中Controller指定方法   
@Around("execution(* com.mingyoutech.mybi.boot.query.dataset.controller.Query_datasetController.exportFile(..))")
  • *:任意返回值
  • (..):任意参数个数、任意参数类型
  • 支持多切点||拼接:方法1() || 方法2()
  • 里面的方法就是切点要处理的方法
  • @Around环绕通知->方法前后都会处理
各种通知类型,编写增强逻辑
注解 执行时机 适用场景 本项目使用
@Before 目标方法执行前 参数校验、预处理 SqlBase64 解码参数
@AfterReturning 方法正常返回后 结果处理 极少使用
@AfterThrowing 方法异常抛出后 异常统一记录 异常日志
@After 方法结束 (无论异常) 资源释放 关闭流
@Around 环绕:方法前后都可拦截,可放行 / 终止 参数校验、拦截终止请求(导出条数限制) ✅本项目导出限制、Sql 解码全部使用
@Around 固定模板
java 复制代码
public Object around(ProceedingJoinPoint joinPoint)throws Throwable{
    // 1.前置增强逻辑
    Object[] args = joinPoint.getArgs();//获取原始入参
    // 自定义拦截:满足条件抛异常,不走原方法
    if(超限){throw new ServiceException("QUERY0002");}
    // 放行原目标方法
    Object result = joinPoint.proceed(args);
    // 后置增强逻辑
    return result;
}

joinPoint.proceed(args):执行原方法;不调用 = 直接截断目标方法(核心拦截原理)。

其他通知类型例子

切点统一:拦截 com.mingyoutech.service..*.*(..) 下所有 service 方法

java 复制代码
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;

@Aspect
@Component
public class AllAspectDemo {

    private static final Logger log = LoggerFactory.getLogger(AllAspectDemo.class);

    // 公共切点表达式,复用
    @Pointcut("execution(* com.mingyoutech.service..*.*(..))")
    public void pointCut(){}


    /**
     * 1. @Before 前置通知:目标方法运行【之前】执行
     * 拿不到返回值;目标方法抛异常,这个也已经执行过了
     */
    @Before("pointCut()")
    public void before(JoinPoint joinPoint){
        // 获取方法名、入参
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        log.info("@Before 前置通知,方法名:{},入参:{}", methodName, args);
    }


    /**
     * 2. @AfterReturning 返回通知:方法【正常执行完毕返回后】执行
     * ⚠️ 如果目标方法抛出异常,此方法不会跑!
     * returning = "retObj" 用来接收目标方法的返回值
     */
    @AfterReturning(value = "pointCut()", returning = "retObj")
    public void afterReturning(JoinPoint joinPoint, Object retObj){
        log.info("@AfterReturning 返回通知,拿到返回值:{}", retObj);
        // 这里可以读取返回值,但**不能修改返回值**!!!
        // 注意:@AfterReturning只能读取,无法改变上层的返回结果;想修改返回只能用@Around
    }


    /**
     * 3. @AfterThrowing 异常通知:目标方法抛出异常的时候才执行
     * throwing = "ex" 接收抛出的异常对象
     */
    @AfterThrowing(value = "pointCut()", throwing = "ex")
    public void afterThrowing(JoinPoint joinPoint, Throwable ex){
        log.error("@AfterThrowing 异常通知,捕获异常:", ex);
    }


    /**
     * 4. @After 最终通知:相当于finally
     * ✅不管方法正常返回 OR 抛出异常,一定会执行
     * 拿不到返回值、拿不到异常;做资源释放、记录日志
     */
    @After("pointCut()")
    public void after(JoinPoint joinPoint){
        log.info("@After 最终通知,一定会执行");
    }


    /**
     * 5. @Around 环绕通知:功能最全,包裹目标方法
     * ProceedingJoinPoint 只能用在@Around
     * 可以控制:要不要执行原方法、修改入参、修改返回值、捕获异常
     */
    @Around("pointCut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        log.info("@Around >>> 环绕:目标方法执行【之前】");

        Object[] args = joinPoint.getArgs();
        Object result;
        try {
            // 执行原始目标方法
            result = joinPoint.proceed(args);
            log.info("@Around >>> 环绕:方法正常执行完成,原始返回值={}", result);

            // ✅环绕独有的能力:修改返回值,替换上层返回
            // result = R.success("切面修改后的返回");

        } catch (Throwable e) {
            log.error("@Around >>> 环绕:捕获业务异常", e);
            // 可以选择:继续抛出异常 或者 返回自定义对象
            throw e;
        } finally {
            log.info("@Around >>> 环绕:finally块,无论成功失败都执行");
        }

        log.info("@Around >>> 环绕:返回处理后结果");
        return result;
    }
}

执行顺序(目标方法抛出异常)

复制代码
1. @Around 前置部分
2. @Before
3. 【执行业务目标方法】
4. @Around 拿到proceed返回之后的代码
5. @AfterReturning
6. @After
7. @Around return

执行顺序(目标方法抛出异常)

复制代码
1. @Around 前置部分
2. @Before
3. 【执行业务目标方法 → 抛出异常】
4. @Around catch块执行
5. @AfterThrowing
6. @After
⚠️ @AfterReturning 不会执行!

三、切面使用时机(什么时候选用 AOP)

适合使用 AOP 场景(横向公共逻辑)
  1. 参数统一预处理:SqlBase64 解码(项目现有切面)、参数脱敏、参数格式转换
  2. 接口拦截校验:导出条数上限校验、接口访问白名单、权限拦截、防重复提交
  3. 全局通用处理:接口耗时统计、统一日志记录、全局异常捕获
  4. 多接口复用规则:同一个校验规则作用在多个接口(findDataCount+exportFile 双接口导出校验)

四、开发注意事项(高频踩坑点)

1、切点相关
  1. 拦截 Jar 包内类 :全限定类名必须写完整包路径,不能简写;必须开启proxyTargetClass=true
  2. 参数下标固定:args[下标]严格对应接口参数顺序,接口修改参数顺序切面同步出错
java 复制代码
// exportFile(HttpServletResponse response,String jdbcid,String format,Map param)
args[0]=response、args[1]=jdbcid、args[3]=param
  1. 不要用this内部调用:同类中 A 方法调用 B 方法,B 方法切面无法拦截(AOP 代理失效)
2、入参空指针问题

获取 param 前先判空:

java 复制代码
if(param!=null&&param.containsKey("queryid"))
3、SpringBean 注入
  1. 切面必须@Component,否则 Spring 不实例化、切面不生效;
  2. final 修饰成员变量:强制构造注入,避免空指针;
  3. Jar 中的 Service 可以正常注入,SpringIoC 可扫描第三方 Jar 的 @Service。
4、@Around 关键注意
  1. proceed()只能调用一次,多次调用会重复执行原接口;
  2. 抛出异常直接终止接口,和业务全局异常处理器联动;
  3. 若需要修改入参:修改args数组元素,proceed(新args)传入修改后的参数。
5、事务失效

切面和事务注解 @Transactional 顺序:AOP 外层、事务内层;切面抛异常会触发事务回滚。

五、速记总结

  1. 切面三板斧:@Component+@Aspect + 切点表达式 + @Around 通知;
  2. 拦截 Jar:proxyTargetClass=true
  3. 多切面排序:@Order;
  4. @Around 核心:proceed 放行、抛异常拦截;
  5. 公共通用逻辑用 AOP,私有业务写在方法内。
相关推荐
测试运维日常笔记1 小时前
Unix/Linux 系统软件包管理笔记
linux·笔记·unix
2301_806709382 小时前
国内知名的生活水箱制造厂有哪些
python·生活
学掌门2 小时前
超级菜鸟怎么学习数据分析?
python·学习·数据分析
鸿芯微控科技2 小时前
压电纳米定位分辨率怎么测?噪声、带宽、最小可检测位移与Python分析
开发语言·python·噪声分析·压电执行器·纳米定位·位移测量
用户7783366132112 小时前
从 0 搭一个关键词排名监控:核心思路 + 可运行代码
后端·python·api
Fanta丶2 小时前
14.Python闭包、装饰器
python
崖边看雾2 小时前
OpenCV 图像处理学习笔记|图像运算、边界填充、阈值处理、图像平滑滤波
图像处理·opencv·学习
呱呱巨基2 小时前
Docker 基础概念学习
linux·c++·学习·docker