
3,AOP工作流程
AOP的入门案例已经完成,对于刚才案例的执行过程,我们就得来分析分析,这一节我们主要讲解两个知识点:AOP工作流程和AOP核心概念。其中核心概念是对前面核心概念的补充。
3.1 AOP工作流程
由于AOP是基于Spring容器管理的bean做的增强,所以整个工作过程需要从Spring加载bean说起:
流程1:Spring容器启动
-
容器启动就需要去加载bean,哪些类需要被加载呢?
-
需要被增强的类,如:BookServiceImpl
-
通知类,如:MyAdvice
-
注意此时bean对象还没有创建成功
流程2:读取所有切面配置中的切入点
-
上面这个例子中有两个切入点的配置,但是第一个
ptx()并没有被使用,所以不会被读取。
流程3:初始化bean,
判定bean对应的类中的方法是否匹配到任意切入点
-
注意第1步在容器启动的时候,bean对象还没有被创建成功。
-
要被实例化bean对象的类中的方法和切入点进行匹配

-
匹配失败,创建原始对象,如
UserDao-
匹配失败说明不需要增强,直接调用原始对象的方法即可。
-
-
匹配成功,创建原始对象(==目标对象==)的==代理==对象,如:
BookDao-
匹配成功说明需要对其进行增强
-
对哪个类做增强,这个类对应的对象就叫做目标对象
-
因为要对目标对象进行功能增强,而采用的技术是动态代理,所以会为其创建一个代理对象
-
最终运行的是代理对象的方法,在该方法中会对原始方法进行功能增强
-
-
流程4:获取bean执行方法
-
获取的bean是原始对象时,调用方法并执行,完成操作
-
获取的bean是代理对象时,根据代理对象的运行模式运行原始方法与增强的内容,完成操作
验证容器中是否为代理对象
为了验证IOC容器中创建的对象和我们刚才所说的结论是否一致,首先先把结论理出来:
-
如果目标对象中的方法会被增强,那么容器中将存入的是目标对象的代理对象
-
如果目标对象中的方法不被增强,那么容器中将存入的是目标对象本身。
验证思路
1.要执行的方法,不被定义的切入点包含,即不要增强,打印当前类的getClass()方法
2.要执行的方法,被定义的切入点包含,即要增强,打印出当前类的getClass()方法
3.观察两次打印的结果
步骤1:修改App类,获取类的类型
public class App {
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
BookDao bookDao = ctx.getBean(BookDao.class);
System.out.println(bookDao);
System.out.println(bookDao.getClass());
}
}
步骤2:修改MyAdvice类,不增强
因为定义的切入点中,被修改成update1,所以BookDao中的update方法在执行的时候,就不会被增强,
所以容器中的对象应该是目标对象本身。
@Component
@Aspect
public class MyAdvice {
@Pointcut("execution(void com.itheima.dao.BookDao.update1())")
private void pt(){}
@Before("pt()")
public void method(){
System.out.println(System.currentTimeMillis());
}
}
步骤3:运行程序

步骤4:修改MyAdvice类,增强
因为定义的切入点中,被修改成update,所以BookDao中的update方法在执行的时候,就会被增强,
所以容器中的对象应该是目标对象的代理对象
@Component
@Aspect
public class MyAdvice {
@Pointcut("execution(void com.itheima.dao.BookDao.update())")
private void pt(){}
@Before("pt()")
public void method(){
System.out.println(System.currentTimeMillis());
}
}
步骤5:运行程序

至此对于刚才的结论,我们就得到了验证,这块大家需要注意的是:
不能直接打印对象,从上面两次结果中可以看出,直接打印对象走的是对象的toString方法,不管是不是代理对象打印的结果都是一样的,原因是内部对toString方法进行了重写。
🧠 理论理解
Spring AOP的核心思想是:基于Spring容器中管理的Bean,在运行时为其动态织入增强功能。整个流程基于代理机制实现,典型的几个关键阶段是:
1️⃣ 容器启动:加载所有需要的Bean,扫描@Aspect注解的切面类、@Pointcut定义的切入点。
2️⃣ 切入点解析:把切入点表达式转为匹配规则,标记哪些方法需要增强。
3️⃣ Bean初始化:
-
如果Bean的方法没有匹配切入点 ➔ 创建原始对象。
-
如果Bean的方法匹配切入点 ➔ 创建目标对象的代理对象。
4️⃣ 调用时织入增强:当你获取Bean调用方法时:
-
代理对象 ➔ 会执行通知(增强逻辑) + 原始方法。
-
原始对象 ➔ 直接执行原始方法。
✅ 结论:Spring AOP是“在创建Bean时就确定是否增强”,增强后的Bean其实是一个“代理对象”。
🏢 企业实战理解
阿里巴巴:在Seata分布式事务框架中,利用Spring AOP实现了方法级事务增强,在代理对象中自动识别是否需要开启/提交/回滚事务。
字节跳动:飞书后台很多接口通过AOP实现日志埋点和接口权限校验,通过切面隔离横切逻辑,业务开发无需重复写校验代码。
Google:在部分微服务框架中,也采用类似Spring AOP的思想来实现请求链路追踪(如OpenTelemetry),增强层与业务解耦。
腾讯云:API网关通过AOP机制实现动态限流与熔断器逻辑,每个API接口的方法调用被代理增强,保障系统高可用。
❓ 面试题 1:Spring AOP 在创建 Bean 时是如何决定用原始对象还是代理对象的?能详细说说这个判断机制吗?
✅ 参考答案:
这个问题本质上考察 Spring AOP 的核心机制。Spring 在容器启动时会加载所有 Bean,但它并不会一上来就创建代理对象,而是在 Bean 实例化时判断:
-
✅ 如果该 Bean 的类或方法没有被切入点匹配,那么直接创建原始对象;
-
✅ 如果该 Bean 的类中至少有一个方法被切入点匹配,那么创建一个代理对象(比如 JDK 动态代理或 CGLIB 代理)。
这一步判断发生在 AbstractAutoProxyCreator 的 wrapIfNecessary() 方法中,它会检查切入点表达式是否匹配当前 Bean 的方法。
比如我在项目中做全局日志埋点时,我们的日志切面只作用在@Service注解的类上,Spring 就是通过扫描这些切面+切入点表达式,判断当前 Bean 是否需要增强的。
一个坑点是:只有 Spring 管理的 Bean 才会被 AOP 增强,如果你手动 new 出来的对象,是不会被代理的!
场景题 1
📝 业务场景:
你在开发一个在线商城系统,Service 层的所有接口都要求在调用前记录访问日志(包括方法名、参数、用户信息等),产品经理又临时加了一个需求:某些接口(如查询商品库存)性能要求极高,不允许加日志埋点。
你采用了 Spring AOP 来实现这个日志功能,但上线后发现有些接口日志失效,有些接口即使配置了不打日志,仍然输出日志。
💬 请你分析这背后的原因,并结合 AOP 工作流程解释解决方案。
✅ 参考解答:
这个场景我在公司也遇到过类似问题,它考察的是:
1️⃣ AOP 的“匹配机制”细节:
AOP 的工作流程是:
-
Spring 在 Bean 创建阶段,根据切入点匹配来决定是否生成代理对象;
-
一旦生成了代理对象,这个代理对象会对匹配的切入点方法织入增强逻辑。
2️⃣ 为什么“日志失效”:
-
有可能是切入点表达式写得太“宽泛”或“错误”,导致日志切面没有命中要增强的方法(比如方法签名变化了,但切入点没更新)。
3️⃣ 为什么“仍然输出日志”:
-
说明该方法虽然配置了“跳过日志”,但代理对象已经生成,并且切入点表达式过于宽泛,没有排除这些高性能方法。
✅ 解决方案:
-
精确调整
@Pointcut表达式,比如用&& !execution(...)排除特定方法; -
或者在切面内部判断注解/参数动态控制是否输出日志(比如
@SkipLog注解结合JoinPoint判断); -
最后重启项目验证代理对象是否是按需生成的。
3.2 AOP核心概念
在上面介绍AOP的工作流程中,我们提到了两个核心概念,分别是:
-
目标对象(Target):原始功能去掉共性功能对应的类产生的对象,这种对象是无法直接完成最终工作的
-
代理(Proxy):目标对象无法直接完成工作,需要对其进行功能回填,通过原始对象的代理对象实现
上面这两个概念比较抽象,简单来说,
目标对象就是要增强的类[如:BookServiceImpl类]对应的对象,也叫原始对象,不能说它不能运行,只能说它在运行的过程中对于要增强的内容是缺失的。
SpringAOP是在不改变原有设计(代码)的前提下对其进行增强的,它的底层采用的是代理模式实现的,所以要对原始对象进行增强,就需要对原始对象创建代理对象,在代理对象中的方法把通知[如:MyAdvice中的method方法]内容加进去,就实现了增强,这就是我们所说的代理(Proxy)。
小结
通过这一节中,我们需要掌握的内容有:
-
能说出AOP的工作流程
-
AOP的核心概念
-
目标对象、连接点、切入点
-
通知类、通知
-
切面
-
代理
-
-
SpringAOP的本质或者可以说底层实现是通过代理模式。
🧠 理论理解
-
目标对象(Target):就是你写的原始业务类(比如
BookDaoImpl),本身功能正常,但不带任何AOP增强功能。 -
代理对象(Proxy):Spring为目标对象生成的增强版对象,当你获取Bean调用方法时,实际执行的是代理对象的方法,它内部“包裹”了通知逻辑。
✅ 核心点:AOP实现=用代理对象替代目标对象,并在调用时织入增强代码。
🏢 企业实战理解
美团点评:在美团的商户入驻系统中,所有涉及数据库操作的方法都被AOP代理为事务增强对象,保障数据一致性。
OpenAI:在早期API接入层,利用AOP机制实现接口层限流、认证、审计日志,通过代理对象将所有API请求的安全逻辑“无侵入式”织入到核心逻辑中。
华为云:利用AOP动态代理机制封装了多租户隔离功能,系统所有DAO层方法自动增强,拦截SQL查询前注入租户ID,保障数据隔离。
❓ 面试题 2:AOP 中的“目标对象”和“代理对象”你怎么理解?它们在执行时各自扮演什么角色?
✅ 参考答案:
这个问题是面试常考的基础点。我的理解是:
-
目标对象(Target Object):就是你业务上原本的类,比如
BookDaoImpl。它是完整实现了你的业务功能的,但不包含任何“横切关注点”(比如日志、事务)。 -
代理对象(Proxy Object):是 Spring 在创建 Bean 时,如果发现某个 Bean 符合 AOP 增强条件,就会为它生成一个代理对象,这个代理对象其实是**“增强版的目标对象”**。当我们调用 Bean 方法时,实际是通过代理对象去调用,这个对象会在内部“织入”增强逻辑,比如日志打印、事务管理、权限校验等。
简单说:
👉 目标对象 = 原始业务逻辑
👉 代理对象 = 原始逻辑 + 横切增强功能
比如在我们公司做权限管理时,核心业务类是目标对象,而我们写的权限校验切面(Aspect)会生成代理对象,调用时是代理对象先做权限校验,再调用目标对象的方法。
场景题 2
📝 业务场景:
你在字节跳动参与开发一个高并发短视频转码服务,团队决定用 AOP 实现“任务执行时间监控”,即每个 Service 层方法执行完毕后,记录耗时到 Prometheus。
你们上线后发现,部分方法的监控数据缺失,尤其是那些 Service 内部自调用的方法没有任何监控数据。
💬 请结合 AOP 的“代理机制”解释这个现象,并说出至少 2 种可行的解决方案。
✅ 参考解答:
这很典型!我理解的问题点是:
-
AOP 底层是通过代理对象实现的,只有通过代理对象发起的调用才会织入切面;
-
当 Service 内部自己调用自己的方法(比如
this.xxx()),其实是绕过了代理对象,直接调用了原始对象的方法,所以不会触发 AOP。
这就是“自调用失效”问题。
✅ 解决方案:
1️⃣ 方法拆分到不同的类,由 Spring 托管不同的 Bean,内部调用通过注入的 Bean 触发切面(推荐做法)。
2️⃣ 使用 AopContext.currentProxy() 获取当前代理对象,再用代理对象调用内部方法,比如:
((MyService) AopContext.currentProxy()).methodB();
这种方案要开启 exposeProxy = true。
3.3 动态代理机制
❓ 面试题 3:Spring AOP 的底层用的是什么代理机制?JDK 动态代理和 CGLIB 有什么区别?
✅ 参考答案:
Spring AOP 的底层是基于 动态代理机制 实现的,有两种策略:
-
JDK 动态代理:基于接口的代理。如果你的目标对象实现了接口,Spring 默认会使用
java.lang.reflect.Proxy来生成代理对象。 -
CGLIB 代理:基于类的代理。如果目标对象没有实现接口,Spring 会用 CGLIB 来动态生成子类,覆盖原始方法实现增强。
它们的区别:
| 对比点 | JDK 动态代理 | CGLIB 动态代理 |
|---|---|---|
| 是否需要接口 | ✅ 需要接口 | ❌ 不需要接口,基于子类 |
| 代理方式 | 反射生成接口的实现类 | ASM字节码生成子类 |
| 性能 | JDK8 之后优化很好,基本一致 | 性能略高(尤其无接口时更适合) |
| 场景 | 推荐有接口的业务场景 | 无接口或想对类进行细粒度控制的场景 |
我在字节跳动的项目里遇到过:有的老项目是用接口式的 Service,AOP 是用 JDK 代理;但有个新模块直接用的纯类实现(无接口),AOP 自动用 CGLIB。
3.4 AOP 实战问题
❓ 面试题 4(进阶):在你做过的项目中,有没有遇到 AOP 代理失效的情况?什么原因?怎么解决的?
✅ 参考答案:
是的,这个问题特别经典。我遇到过的场景:
问题场景:
在 ServiceImpl 类里,我们的方法 A 调用方法 B,B 方法上加了日志切面,但是发现调用时B 的切面没有生效。
原因:
这是因为 Spring AOP 是通过代理实现的,而 A 方法内部直接调用 B 方法时,是目标对象内部方法的自调用,绕过了代理对象,所以不会触发 AOP。
解决方法:
1️⃣ 从外部调用:把 A 和 B 方法放在不同的类中,让 Spring 注入的 Bean 调用,就会经过代理对象。
2️⃣ 获取当前代理对象:Spring 提供了 AopContext.currentProxy() 方法,可以在方法内部获取到当前代理对象,从而用代理对象去调用方法。
比如:
((BookService) AopContext.currentProxy()).B();
企业里(比如我在美团做的项目)常用的是方案 1,因为它符合单一职责,也方便维护。
3.5 结合事务机制
❓ 面试题 5:Spring 的事务是怎么结合 AOP 实现的?底层大概流程是怎样的?
✅ 参考答案:
Spring 的事务本质上是通过 AOP 实现的!事务的切面(比如 @Transactional)会生成一个增强器(Advisor),在方法执行前开启事务,方法执行后提交/回滚事务。
底层流程:
1️⃣ 加载 @Transactional 切面;
2️⃣ 在方法调用前执行 TransactionInterceptor 的 invoke 方法;
3️⃣ 内部先获取 PlatformTransactionManager,开启事务;
4️⃣ 执行原始方法(比如 DAO 操作);
5️⃣ 方法正常结束 ➔ 提交事务;
方法抛出异常 ➔ 回滚事务。
在我工作中(如阿里系微服务中间件 Seata),AOP 事务是基础设施,我们调业务代码时几乎感知不到事务逻辑,AOP 帮我们自动完成了。
场景题 3
📝 业务场景:
你维护的系统中有一个 AOP 切面,用来做接口鉴权校验。但是有一天排查问题时发现,部分动态生成的 Bean(如 MyBatis 动态代理的 Mapper 接口)居然也被切面拦截,导致异常。
产品要求只对 @Service 注解的类进行鉴权。
💬 请问:你会怎么调整切面逻辑来实现这个目标?为什么之前会有这个问题?
✅ 参考解答:
这个问题的本质是切入点表达式太“宽泛”导致的。
比如原先的切入点表达式是:
@Pointcut("execution(* com.xxx..*.*(..))")
它会匹配所有包下的方法,MyBatis 的 Mapper 接口虽然是动态代理,但它的接口方法也符合这个表达式,所以被误拦截了。
✅ 调整方法:
我会改为:
@Pointcut("@within(org.springframework.stereotype.Service)")
这个表达式的意思是:只匹配被 @Service 注解标注的类,这样就能精确拦截到目标业务 Service,排除掉 Mapper、工具类等。
也可以结合 bean() 表达式做更精确控制,比如:
@Pointcut("within(@org.springframework.stereotype.Service *) && bean(*Service)")
✅ 原因解释:
因为 AOP 工作流程是根据切入点来生成代理对象的,只要方法签名符合切入点表达式,不管是动态代理还是自己写的类都会被拦截。

4468

被折叠的 条评论
为什么被折叠?



