本篇为整套 AOP 教程第一部分,完整覆盖 AOP 定义、业务场景、原生硬编码弊端、AOP 优势、快速开发完整实操、五大核心专业术语、底层动态代理执行原理,配套完整可运行 Java 代码,零基础看懂切面思想,适合课堂预习、期末复习、面试基础备考;中篇讲解五大通知、切点表达式、JoinPoint 工具类、多切面执行顺序;下篇企业实战操作日志 + ThreadLocal 线程共享。
一、什么是 AOP(面向切面编程)
1. 基础定义
AOP 全称 Aspect Oriented Programming ,中文译为面向切面编程。通俗理解:不修改原有业务代码,批量拦截项目中一批指定方法,统一植入通用公共逻辑(计时、日志、权限、事务)。传统开发是面向对象 OOP,AOP 是对 OOP 的补充优化,解决重复通用代码冗余问题。
1. 典型业务使用场景
- 统计所有业务方法执行耗时,定位慢接口;
- 全局操作日志自动记录(增删改操作留痕);
- 接口统一权限校验;
- 全局事务统一管理;
- 统一异常捕获、返回统一格式结果。
1. 案例需求演示:统计 Service 方法执行耗时
现有部门业务层 DeptServiceImpl,包含 list、delete、getById 等业务方法,需求打印每个方法执行消耗毫秒数。
方式 1:传统原生硬编码实现(高耦合、大量重复代码)
@Service
public class DeptServiceImpl implements DeptService {
@Autowired
private DeptMapper deptMapper;
@Override
public List<Dept> list() {
// 前置计时代码
long beginTime = System.currentTimeMillis();
// 核心业务
List<Dept> deptList = deptMapper.list();
// 后置统计打印
long endTime = System.currentTimeMillis();
log.info("list方法执行耗时:{} ms", endTime - beginTime);
return deptList;
}
@Override
public void delete(Integer id) {
long beginTime = System.currentTimeMillis();
deptMapper.delete(id);
long endTime = System.currentTimeMillis();
log.info("delete方法执行耗时:{} ms", endTime - beginTime);
}
@Override
public Dept getById(Integer id) {
long beginTime = System.currentTimeMillis();
Dept dept = deptMapper.getById(id);
long endTime = System.currentTimeMillis();
log.info("getById方法执行耗时:{} ms", endTime);
return dept;
}
}
原生写法四大致命缺陷
- 代码大量重复:每个方法都要写获取开始时间、结束时间、打印日志三段相同代码;
- 业务代码被污染:核心业务逻辑和计时无关代码混在一起,可读性极差;
- 耦合度极高:后续需要修改日志格式、新增耗时阈值告警,必须修改全部业务方法;
- 极易遗漏:新增业务方法后,忘记添加计时代码,统计数据缺失。
方式 2:AOP 无侵入实现(最优方案)
仅单独新建切面类,所有 Service 方法自动拦截计时,业务代码无需改动一行:
import lombok.extern.slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Slf4j
@Aspect // 标识当前类为切面类
@Component // 交给Spring容器管理
public class RecordTimeAspect {
// 切点:匹配service.impl包下所有类、所有任意参数方法
@Around("execution(* com.itheima.service.impl.*.*(..))")
public Object recordTime(ProceedingJoinPoint pjp) throws Throwable {
// 1. 方法执行前记录起始时间
long beginTime = System.currentTimeMillis();
// 2. 执行原始业务方法
Object result = pjp.proceed();
// 3. 方法执行结束,计算耗时并打印日志
long endTime = System.currentTimeMillis();
log.info("【{}】方法执行耗时:{} ms", pjp.getSignature().getName(), endTime - beginTime);
// 返回原始方法结果
return result;
}
}
改造后的业务层代码,只保留纯粹业务逻辑,无任何冗余计时代码:
@Service
public class DeptServiceImpl implements DeptService {
@Autowired
private DeptMapper deptMapper;
@Override
public List<Dept> list() {
return deptMapper.list();
}
@Override
public void delete(Integer id) {
deptMapper.delete(id);
}
@Override
public Dept getById(Integer id) {
return deptMapper.getById(id);
}
}
1.3 AOP 四大核心优势
- 消除重复代码:通用增强逻辑只编写一次,批量作用于成千上百个方法;
- 代码无侵入:完全不修改原有业务代码,低耦合,不影响原有业务逻辑;
- 开发效率大幅提升:新增业务方法自动被切面拦截,无需重复编写通用逻辑;
- 便于统一维护:修改切面一处代码,全局所有拦截方法同步更新逻辑。
二、SpringAOP 快速入门完整实操步骤
步骤 1:pom.xml 引入 AOP 起步依赖
SpringBoot 项目无需指定版本,父工程统一管理版本号
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
步骤 2:创建切面类,添加两个核心注解
@Component:将切面类交给 Spring IOC 容器实例化,成为 Bean;@Aspect:标记当前类为切面,Spring 识别为 AOP 增强类。
步骤 3:定义切点表达式 + 编写通知方法
使用@Around环绕通知包裹目标方法前后逻辑,ProceedingJoinPoint 调用proceed()执行原始业务方法。
完整入门切面代码(计时案例)
@Slf4j
@Aspect
@Component
public class RecordTimeAspect {
// execution切点匹配所有业务层方法
@Around("execution(* com.itheima.service.impl.*.*(..))")
public Object recordTime(ProceedingJoinPoint pjp) throws Throwable {
// 前置增强逻辑:记录开始时间
long start = System.currentTimeMillis();
// 执行原始目标方法
Object res = pjp.proceed();
// 后置增强逻辑:计算耗时打印日志
long end = System.currentTimeMillis();
log.info("方法执行耗时:{} 毫秒", end - start);
return res;
}
}
执行效果
调用任意 DeptService 的 list/delete/getById 方法,控制台自动打印对应方法执行耗时,无需改动业务代码。
三、AOP 五大核心专业名词(面试必背)
结合计时案例图解概念,清晰区分每个术语含义:
-
连接点 JoinPoint 项目中所有可被 AOP 拦截的方法都叫连接点。案例中 DeptServiceImpl 的 list、delete、getById 全是连接点。
-
切入点 PointCut 筛选匹配规则(execution 表达式),用来过滤需要增强的连接点;只有匹配上切点规则的方法,才会执行切面通知逻辑。案例中
execution(* com.itheima.service.impl.*.*(..))就是切入点。 -
通知 Advice 切面中编写的通用增强逻辑代码,就是通知;本案例中计时、打印日志的代码块属于环绕通知 @Around。通知分为 5 种类型(中篇详细讲解)。
-
切面 Aspect切面 = 切入点 + 通知;同时包含切点匹配规则、通用增强逻辑的完整类(RecordTimeAspect 整个类就是切面)。
-
目标对象 Target被切面拦截、增强的原始业务对象;案例中 DeptServiceImpl 实例就是目标对象。
四、SpringAOP 底层实现:动态代理机制
执行底层流程
- 项目启动时,Spring 扫描所有切面类、所有业务 Bean;
- 识别匹配切点的目标类,动态生成代理对象;
- Controller 注入的 DeptService,实际拿到的是代理对象,而非原始目标对象;
- 调用代理对象 list () 方法时,先执行切面通知(计时),再调用原始目标方法;
- 原始方法执行完毕后,回到代理对象执行后置增强逻辑,最后返回结果。
伪代码直观理解代理类逻辑
// Spring动态生成的代理类伪代码
public class DeptServiceProxy implements DeptService {
// 持有原始目标对象
private DeptService target = new DeptServiceImpl();
@Override
public List<Dept> list() {
// 切面前置增强
long begin = System.currentTimeMillis();
// 执行原始业务方法
List<Dept> list = target.list();
// 切面后置增强
long end = System.currentTimeMillis();
log.info("耗时:{}ms", end - begin);
return list;
}
}
代理优势
开发者无需手动编写代理类,Spring 容器自动完成动态代理创建,完全透明化。
五、上篇全文总结
- AOP 面向切面编程,核心思想:抽取通用逻辑,批量增强目标方法,不侵入业务代码;
- 原生硬编码方式重复冗余、耦合高,AOP 方案四大优势:复用、无侵入、高效、易维护;
- SpringAOP 入门三步:引入 aop 依赖 → 切面类加 @Aspect+@Component → @Around 编写增强逻辑;
- 五大核心概念:连接点、切入点、通知、切面、目标对象;
- 底层原理:Spring 动态生成代理对象,拦截目标方法执行前后自定义逻辑。
上篇拓展实操练习
- 新建 SpringBoot 项目,导入 AOP 起步依赖;
- 编写 DeptService 业务层增删改查方法;
- 编写计时切面,拦截所有 Service 方法打印执行耗时;
- 新增一个 UserService,测试自动被切面拦截。
上篇面试高频考点
- 什么是 AOP?AOP 适用哪些业务场景?
- 传统硬编码实现统计耗时有什么缺陷?
- AOP 五大核心名词分别是什么?
- SpringAOP 底层依靠什么技术实现?
- @Aspect 和 @Component 两个注解各自作用?