沧州市网站建设_网站建设公司_C#_seo优化
2026/1/21 19:19:11 网站建设 项目流程

AOP 学习笔记

一、AOP 基础认知

1. 什么是 AOP

  • 定义:AOP(Aspect Oriented Programming)即面向切面编程,核心是面向特定方法编程,将重复逻辑与业务逻辑分离,实现功能增强。
  • 本质:不修改原始业务代码,通过动态代理技术对目标方法进行增强,解决代码重复、侵入性强的问题。

2. AOP 核心优势

3. 核心概念

概念定义
连接点(JoinPoint)可被 AOP 控制的方法(如业务层所有方法),封装了方法执行时的相关信息
通知(Advice)抽取的重复逻辑(共性功能),体现为具体方法(如计时、日志记录)
切入点(PointCut)匹配连接点的条件(通过表达式描述),决定通知应用于哪些方法
切面(Aspect)通知与切入点的对应关系(通知+切入点),所在类为切面类(@Aspect 标识)
目标对象(Target)通知所应用的原始业务对象
代理对象Spring AOP 底层通过动态代理生成,用于增强目标对象的方法

4. 底层原理

Spring AOP 基于动态代理技术实现:程序运行时自动为目标对象生成代理对象,在代理对象中嵌入通知逻辑,实现对原始方法的增强。

二、AOP 入门实战

1. 需求

统计部门管理业务层方法的执行耗时。

2. 实现步骤

(1)导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
(2)编写切面类
@Component
@Aspect // 标识为切面类
@Slf4j
public class RecordTimeAspect {
// 环绕通知 + 切入点表达式(匹配目标方法)
@Around("execution(* com.itheima.service.impl.DeptServiceImpl.*(..))")
public Object recordTime(ProceedingJoinPoint pjp) throws Throwable {
// 1. 记录开始时间
long begin = System.currentTimeMillis();
// 2. 执行原始业务方法
Object result = pjp.proceed();
// 3. 记录结束时间并计算耗时
long end = System.currentTimeMillis();
log.info("方法执行耗时: {}毫秒", end - begin);
// 4. 返回原始方法结果
return result;
}
}
(3)测试效果

启动服务后,调用业务接口,控制台会输出对应方法的执行耗时。

3. 常见应用场景

三、AOP 进阶知识

1. 通知类型

Spring AOP 提供 5 种通知类型,覆盖方法执行的不同阶段:

通知注解执行时机注意事项
@Around(环绕通知)目标方法执行前 + 执行后需调用 proceed() 执行原始方法,必须返回结果
@Before(前置通知)目标方法执行前无返回值,不能阻止目标方法执行
@After(后置通知)目标方法执行后(无论是否抛出异常)无返回值
@AfterReturning目标方法正常执行完成后(无异常)可获取方法返回值
@AfterThrowing目标方法抛出异常后可获取异常信息
通知执行顺序(无异常情况)

@Around(前)@Before → 目标方法 → @Around(后)@AfterReturning@After

异常情况

@Around(前)@Before → 目标方法(抛异常) → @AfterThrowing@After@Around 后续逻辑不执行)

2. 切入点表达式

用于描述需要匹配的目标方法,核心有 2 种形式:

(1)execution 表达式(常用)
(2)@annotation 表达式(灵活匹配)

通过自定义注解标记目标方法,适合无规则的方法匹配:

  1. 定义自定义注解:
    @Target(ElementType.METHOD) // 仅作用于方法
    @Retention(RetentionPolicy.RUNTIME) // 运行时生效
    public @interface LogOperation {}
  2. 在目标方法上添加注解:
    @Service
    public class DeptServiceImpl implements DeptService {
    @Override
    @LogOperation // 标记需要增强的方法
    public void delete(Integer id) {
    deptMapper.delete(id);
    }
    }
  3. 切面类中引用注解:
    @Before("@annotation(com.itheima.anno.LogOperation)")
    public void before(JoinPoint joinPoint) {
    log.info("前置通知:记录操作日志");
    }
切入点表达式复用

使用 @Pointcut 抽取公共表达式,避免重复编写:

@Aspect
@Component
public class MyAspect {
// 抽取公共切入点表达式
@Pointcut("execution(* com.itheima.service.*.*(..))")
private void pt() {}
// 引用公共表达式
@Before("pt()")
public void before() {
log.info("前置通知...");
}
}

3. 通知顺序控制

当多个切面类匹配同一个目标方法时,通过以下方式控制执行顺序:

  1. 默认规则:按切面类名的字母顺序排序(前置通知:字母靠前先执行;后置通知:字母靠前后执行)
  2. @Order 注解(推荐):在切面类上添加 @Order(数字),数字越小,优先级越高
    @Aspect
    @Component
    @Order(1) // 优先级高于 Order(2) 的切面
    public class MyAspect1 { ... }

四、AOP 案例:操作日志记录

1. 需求

记录系统中增删改接口的操作日志,包含:操作人、操作时间、类名、方法名、参数、返回值、执行时长,存入数据库。

2. 实现步骤

(1)准备工作
  1. 数据库表设计:
    create table operate_log(
    id int unsigned primary key auto_increment comment 'ID',
    operate_emp_id int unsigned comment '操作人ID',
    operate_time datetime comment '操作时间',
    class_name varchar(100) comment '操作的类名',
    method_name varchar(100) comment '操作的方法名',
    method_params varchar(1000) comment '方法参数',
    return_value varchar(2000) comment '返回值',
    cost_time int comment '方法执行耗时(ms)'
    ) comment '操作日志表';
  2. 实体类 OperateLog + Mapper 接口(提供 insert 方法)
(2)编码实现
  1. 自定义注解 @LogOperation(标记需要记录日志的接口方法)
  2. 切面类实现:
    @Aspect
    @Component
    public class OperationLogAspect {
    @Autowired
    private OperateLogMapper operateLogMapper;
    @Around("@annotation(com.itheima.anno.LogOperation)")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
    // 1. 记录开始时间
    long startTime = System.currentTimeMillis();
    // 2. 执行原始方法
    Object result = joinPoint.proceed();
    // 3. 计算耗时
    long costTime = System.currentTimeMillis() - startTime;
    // 4. 构建日志对象
    OperateLog log = new OperateLog();
    log.setOperateEmpId(CurrentHolder.getCurrentId()); // 从 ThreadLocal 获取当前登录人ID
    log.setOperateTime(LocalDateTime.now());
    log.setClassName(joinPoint.getTarget().getClass().getName()); // 目标类名
    log.setMethodName(joinPoint.getSignature().getName()); // 目标方法名
    log.setMethodParams(Arrays.toString(joinPoint.getArgs())); // 方法参数
    log.setReturnValue(result.toString()); // 返回值
    log.setCostTime(costTime);
    // 5. 保存日志到数据库
    operateLogMapper.insert(log);
    return result;
    }
    }
  3. 在 Controller 层增删改方法上添加 @LogOperation 注解

3. 关键技术:ThreadLocal 共享登录信息

  • 作用:在同一线程(同一请求)中共享数据(如当前登录人ID),实现线程隔离
  • 工具类实现
    public class CurrentHolder {
    private static final ThreadLocal<Integer> CURRENT_LOCAL = new ThreadLocal<>();// 设置当前登录人IDpublic static void setCurrentId(Integer empId) {CURRENT_LOCAL.set(empId);}// 获取当前登录人IDpublic static Integer getCurrentId() {return CURRENT_LOCAL.get();}// 移除数据(避免内存泄漏)public static void remove() {CURRENT_LOCAL.remove();}}
  • 使用场景:在 Token 过滤器中解析登录人ID并存入 ThreadLocal,在 AOP 中直接获取。

四、核心总结

  1. AOP 核心思想是分离共性逻辑与业务逻辑,通过切面类统一管理增强功能
  2. 5 种通知类型覆盖方法执行全生命周期,@Around 功能最强大(可控制原始方法执行)
  3. 切入点表达式两种形式:execution(按方法签名匹配)、@annotation(按注解匹配)
  4. 多切面执行顺序通过 @Order 控制,数字越小优先级越高
  5. 实际开发中常用 AOP 实现日志、权限、事务等非业务功能,提高代码复用性和可维护性

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询