一、前置知识:OOP 与 AOP 的核心区别
在学习 AOP 之前,必须先理解 OOP,二者是互补而非替代的关系,也是 Java 开发两大核心编程思想。
1. OOP 面向对象编程
核心思想:垂直模块化 ,以「类、对象、继承、多态、封装」为核心,根据业务模块拆分代码。
例如:用户模块、订单模块、支付模块,拆分不同的 Service、Controller,解决核心业务代码臃肿的问题。
但 OOP 存在致命短板:无法处理跨模块的通用重复代码。
2. AOP 面向切面编程
核心思想:横向切面化,以「通用功能动作」为核心,剥离所有业务模块中重复的通用逻辑。
在日常开发中,大量通用功能不属于核心业务,但几乎所有接口、业务方法都需要使用,例如:
-
基础功能:日志记录、接口耗时统计、参数打印
-
权限功能:登录拦截、角色权限校验、接口访问拦截
-
事务功能:数据库事务开启、提交、回滚
-
高级功能:接口加解密、流量限流、重复提交拦截、全局异常处理
如果通过硬编码写在每一个业务方法中,会出现三大问题:
-
代码冗余:90% 的方法存在重复模板代码
-
耦合度极高:通用功能和核心业务代码绑定,违背「单一职责原则」
-
维护成本高:通用逻辑修改时,需要改动全项目所有业务方法,极易漏改、出错
二、AOP 核心定义与核心价值
1. 定义
AOP(Aspect Oriented Programming,面向切面编程),是 Spring 框架重要的拓展编程思想,基于动态代理机制,在不修改原有业务代码的前提下,对目标方法进行统一的功能增强。
2. 核心价值
彻底实现 业务代码与通用工具代码解耦,遵循:开闭原则、单一职责原则。
-
对原有业务代码:无侵入(不用改一行业务代码)
-
对通用功能:统一维护、全局生效、按需开启
三、AOP 七大核心专业概念
这七个概念是 AOP 的基石,所有 AOP 功能都围绕这七个概念运行:
1. 连接点(JoinPoint)
所有可以被 AOP 拦截的位置 。在 Spring AOP 中,仅支持方法级别拦截,所以连接点特指:项目中所有的 public 方法。
简单理解:所有可选的增强候选方法。
2. 切点(Pointcut)
从海量连接点中,通过表达式筛选出来、真正需要被增强的目标方法。
简单理解:连接点是全员,切点是筛选后的精准目标。
3. 通知(Advice)
拦截到目标方法后,需要执行的通用增强逻辑(日志、权限、事务等),定义了「什么时候执行、执行什么代码」。
4. 切面(Aspect)
切面 = 切点 + 通知。是一个独立的 Java 类,专门用来封装所有 AOP 增强逻辑,统一管理拦截规则和增强代码。
5. 目标对象(Target)
被切面拦截、被功能增强的原始业务对象(例如 UserService、OrderService),内部只包含核心业务代码,无任何通用冗余逻辑。
6. 代理对象(Proxy)
Spring 框架在运行时动态生成的代理对象,用于包裹目标对象,拦截方法调用,执行切面增强逻辑。用户调用目标方法时,实际调用的是代理对象的方法。
7. 织入(Weaving)
将切面的增强代码,动态植入到目标方法执行流程中的过程。Spring AOP 属于运行时织入,项目启动后、方法调用时动态完成,无需编译。
四、AOP 五大通知类型
五种通知覆盖了方法执行的全生命周期,不同通知有严格的执行顺序和使用场景,也是开发易错重点:
| 注解 | 通知名称 | 执行时机 | 适用场景 |
|---|---|---|---|
| @Before | 前置通知 | 目标方法执行之前 | 参数校验、权限校验、参数打印 |
| @AfterReturning | 返回通知 | 目标方法正常执行完毕,无异常 | 记录成功日志、统计成功接口、处理返回结果 |
| @AfterThrowing | 异常通知 | 目标方法抛出异常时执行 | 异常日志记录、异常告警、事务回滚 |
| @After | 后置通知 | 目标方法结束(无论正常/异常,必执行) | 资源释放、统一收尾操作 |
| @Around | 环绕通知 | 包裹整个目标方法,权限最高 | 耗时统计、限流、重复提交拦截、万能增强 |
重点补充:基础执行顺序(无环绕通知)
@Before → 目标业务方法执行 → @AfterReturning(正常)/@AfterThrowing(异常) → @After
如果存在环绕通知:环绕通知优先级最高,所有普通通知都会嵌套在环绕通知内部执行。
五、基础实战:包路径拦截 AOP(全局统一日志)
1. 项目依赖(SpringBoot 必备)
所有 SpringBoot AOP 开发,必须引入 aop 启动器,内置 AspectJ 核心能力
html
<!-- Spring AOP 核心依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2. 切面类代码(全局拦截 Service 层方法)
通过 execution 切点表达式,实现包粒度全局拦截,统一打印方法执行日志
java
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
// 标识当前类为切面类
@Aspect
// 交给Spring容器管理
@Component
public class SimpleAopAspect {
// 切点表达式:拦截 com.yyf.service 包下所有类、所有参数、所有返回值的方法
@Pointcut("execution(* com.yyf.service.*.*(..))")
public void servicePointCut(){}
// 前置通知:方法执行前触发
@Before("servicePointCut()")
public void before(JoinPoint joinPoint){
// 获取目标方法名
String methodName = joinPoint.getSignature().getName();
// 获取方法入参
Object[] args = joinPoint.getArgs();
System.out.println("【前置通知】方法【"+methodName+"】开始执行,入参:"+ args);
}
// 返回通知:方法无异常执行完成触发
@AfterReturning("servicePointCut()")
public void afterReturn(){
System.out.println("【返回通知】方法正常执行完成");
}
// 异常通知:方法抛出异常触发
@AfterThrowing("servicePointCut()")
public void afterThrow(){
System.err.println("【异常通知】方法执行出现异常");
}
// 后置通知:方法最终结束,必定触发
@After("servicePointCut()")
public void after(){
System.out.println("【后置通知】方法执行结束(无论是否报错)");
}
}
3. 测试业务类 & 测试用例
html
@Service
public class UserService {
public void getUserInfo(){
System.out.println("===== 业务方法:查询用户信息 =====");
}
}
java
@SpringBootTest
public class AopTest {
@Autowired
private UserService userService;
@Test
void testAop(){
userService.getUserInfo();
}
}
4. 执行结果与流程解析
java
【前置通知】方法【getUserInfo】开始执行,入参:[]
===== 业务方法:查询用户信息 =====
【返回通知】方法正常执行完成
【后置通知】方法执行结束(无论是否报错)
优势:全局统一生效,无需修改任何业务代码;缺点:粒度太粗,全局泛滥,不需要增强的方法也会被拦截。因此企业开发主流使用「自定义注解 AOP」
六、自定义注解 AOP--精准拦截
全局包拦截通用性差,生产环境几乎全部使用自定义注解切面:仅对添加注解的方法做增强,精准可控、灵活度极高,常用于操作日志、接口统计、权限控制。
步骤1:创建自定义注解
java
package com.yyf.Config;
import java.lang.annotation.*;
// 仅作用于方法
@Target(ElementType.METHOD)
// 运行时保留,允许反射获取注解信息
@Retention(RetentionPolicy.RUNTIME)
public @interface Logprint {
// 自定义属性:用于备注模块名称、接口描述
String value() default "";
}
步骤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.springframework.stereotype.Component;
@Aspect
@Component
public class LogPrintAspect {
// 切点:仅拦截带有 @Logprint 注解的方法
@Pointcut("@annotation(com.yyf.Config.Logprint)")
public void logPrintPointcut(){}
@Around("logPrintPointcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
// 1. 方法执行前逻辑
long startTime = System.currentTimeMillis();
String methodName = joinPoint.getSignature().getName();
System.out.println("[日志AOP] 方法【"+methodName+"】开始执行");
// 核心代码:执行原始业务方法(必须存在,否则业务代码失效)
Object result = joinPoint.proceed();
// 2. 方法执行后逻辑
long costTime = System.currentTimeMillis() - startTime;
System.out.println("[日志AOP] 方法【"+methodName+"】执行结束,耗时:" + costTime + "ms");
// 返回业务方法结果
return result;
}
}
步骤3:业务层使用注解
java
@Service
public class OrderService {
// 添加注解:当前方法被AOP精准增强
@Logprint("订单查询模块")
public void getOrder(){
System.out.println("===== 业务:查询订单 =====");
}
// 无注解:不触发AOP增强
public void noLogMethod(){
System.out.println("===== 无AOP增强方法 =====");
}
}
执行效果
java
[日志AOP] 方法【getOrder】开始执行
===== 业务:查询订单 =====
[日志AOP] 方法【getOrder】执行结束,耗时:0ms
七、核心对象详解
1. JoinPoint(普通通知专用)
适用于 @Before、@After、@AfterReturning、@AfterThrowing,用于获取目标方法基础信息
-
getSignature().getName():获取方法名 -
getArgs():获取方法所有入参 -
getTarget():获取原始目标对象 -
getSignature().getDeclaringType():获取目标类名
1.1 Signature 方法签名对象(补充一手)
代码中频繁出现的 getSignature(),它是 AOP 开发高频核心对象。
Signature 释义:方法签名对象。
在 AOP 中:JoinPoint(连接点)代表当前拦截的方法 ,而 Signature 是这个方法的详细信息描述对象,专门用来「读取当前拦截方法的全部元数据」。
简单通俗理解:
JoinPoint = 整个被拦截的方法本体
Signature = 这个方法的「身份证信息」(名字、所属类、返回值、方法全称等)
Signature 是一个接口,在方法拦截中,实际实现类为 MethodSignature,专门用于封装 Java 方法信息。
Signature 常用全套方法(教学+开发全覆盖)
-
signature.getName():获取简单方法名(例如:getUserInfo) -
signature.toShortString():获取精简方法签名(类名+方法名) -
signature.toLongString():获取完整方法签名(包含返回值、参数列表) -
signature.getDeclaringType():获取方法所属的类 Class 对象 -
signature.getDeclaringTypeName():获取方法所属类的全限定名(包名+类名)
java
@Before("servicePointCut()")
public void before(JoinPoint joinPoint){
// 获取方法签名对象
Signature signature = joinPoint.getSignature();
// 简单方法名
String methodName = signature.getName();
// 所属类全路径
String className = signature.getDeclaringTypeName();
System.out.println("所属类:"+className);
System.out.println("方法名:"+methodName);
System.out.println("完整方法信息:"+signature.toLongString());
}
直接通过 JoinPoint 获取方法信息,本质都是:JoinPoint 底层调用了 Signature 对象的方法。JoinPoint 本身不存储方法数据,所有方法元数据全部由 Signature 提供。
2. ProceedingJoinPoint(环绕通知专用)
继承自 JoinPoint,仅 @Around 可以使用,拥有核心独有能力:
-
proceed():手动执行原始业务方法 -
可自定义拦截逻辑:条件不满足可直接拦截方法、不执行业务代码
-
可捕获方法返回值、捕获异常,自由度最高
八、常用切点表达式汇总
切点表达式是 AOP 开发核心,掌握以下四种即可覆盖 99% 开发场景:
-
指定包拦截 :
execution(* com.yyf.service.*.*(..))拦截service层所有方法 -
Controller全局拦截 :
execution(* com.yyf.controller.*.*(..)) -
注解拦截(精准) :
@annotation(完整注解类全路径) -
指定类拦截 :
execution(* com.yyf.service.UserService.*(..))
表达式释义:execution(返回值 包.类.方法(参数)),* 代表任意,(..) 代表任意参数
九、AOP 底层原理与动态代理机制
1. 底层核心
Spring AOP 本质:基于动态代理实现的方法增强,无代码侵入,运行时动态创建代理对象。
2. 两种代理机制
-
JDK 动态代理 :目标类实现了接口时使用,基于接口代理,只能拦截接口定义的方法
-
CGLIB 动态代理 :目标类无接口时使用,基于子类继承代理,可拦截类中所有方法
SpringBoot 2.0+ 默认统一使用 CGLIB 代理,无需手动配置。
3. AOP 完整执行流程
-
Spring 项目启动,扫描所有添加
@Aspect的切面类,交给容器管理 -
解析切面中的切点表达式,匹配项目中所有目标方法
-
根据目标类是否有接口,动态生成对应代理类
-
客户端调用业务方法时,优先调用代理对象
-
代理对象执行切面增强逻辑,再调用原始目标方法
-
方法执行完毕,完成收尾增强逻辑
十、个人理解易错点总结
-
AOP 只能拦截public 方法,private、static 方法无法拦截
-
@Around 优先级高于所有普通通知,是功能最全的通知
-
proceed()必须手写,否则原始业务方法不会执行 -
@After 无论方法是否异常都会执行,@AfterReturning 仅成功执行
-
全局包拦截适合统一基础监控,注解拦截适合精准业务增强