SSM - Spring_day03 - 3.AOP工作流程

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 代理)。

这一步判断发生在 AbstractAutoProxyCreatorwrapIfNecessary() 方法中,它会检查切入点表达式是否匹配当前 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️⃣ 在方法调用前执行 TransactionInterceptorinvoke 方法;
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 工作流程是根据切入点来生成代理对象的,只要方法签名符合切入点表达式,不管是动态代理还是自己写的类都会被拦截。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

夏驰和徐策

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值