Spring AOP 底层原理

ps:如果这篇帖子对于还在找工作和找实习的你有所帮助,可以关注我,给本贴点赞、评论、收藏并订阅专栏;同时不要吝啬您的花花

Spring AOP(Aspect-Oriented Programming,面向切面编程)并非独立的技术框架,而是基于动态代理实现的横向代码增强方案,核心目标是将日志、事务、权限校验等横切逻辑与业务逻辑解耦,避免代码冗余。其底层本质是:在运行时生成目标对象的代理对象,通过代理对象拦截目标方法,在方法执行前后植入横切逻辑

一、AOP核心前置认知

理解底层原理前,先明确AOP的核心术语与设计初衷,避免混淆概念:

  • 切面(Aspect):封装横切逻辑的载体,由切点+通知组成,比如事务切面、日志切面。
  • 切点(Pointcut):定义哪些类、哪些方法需要被拦截,通过表达式匹配目标方法。
  • 通知(Advice):定义横切逻辑的执行时机,分为前置、后置、环绕、异常、最终通知。
  • 连接点(JoinPoint):程序执行过程中可被拦截的点,Spring AOP仅支持方法执行连接点。
  • 织入(Weaving):将切面逻辑植入目标方法的过程,Spring AOP采用运行时织入。

Spring AOP 不依赖编译期增强,全程基于JVM运行时动态代理完成织入,这是它与AspectJ(编译期织入)的核心区别。

二、底层核心:动态代理机制

Spring AOP的所有功能都建立在动态代理之上,框架会根据目标对象的类型,自动选择两种代理方案:JDK动态代理CGLIB动态代理

2.1 JDK动态代理(基于接口)

当目标对象实现了至少一个接口时,Spring默认选用JDK动态代理,属于JDK原生代理方案,无需引入第三方依赖。

核心原理

  • 基于反射实现,要求目标类必须实现接口,代理对象与目标对象实现同一接口。
  • 核心组件:InvocationHandler(调用处理器)、Proxy(代理生成器)。
  • 执行逻辑:通过Proxy.newProxyInstance()生成代理对象,调用目标方法时,会先进入InvocationHandler.invoke()方法,在该方法中植入横切逻辑,再反射调用目标方法。

局限性

只能代理实现了接口的类,无法代理普通类,且反射调用存在轻微性能损耗。

2.2 CGLIB动态代理(基于类)

当目标对象没有实现任何接口时,Spring自动切换为CGLIB(Code Generation Library)动态代理,属于字节码增强框架。

核心原理

  • 基于字节码生成实现,通过继承目标类,生成子类作为代理对象,重写目标方法完成增强。
  • 核心组件:MethodInterceptor(方法拦截器)、Enhancer(增强器)。
  • 执行逻辑:通过Enhancer生成目标类的子类,调用目标方法时,先进入MethodInterceptor.intercept()方法,执行横切逻辑后再调用父类(目标类)的方法。

局限性

无法代理final类、final方法,因为子类无法重写final修饰的结构。

2.3 Spring代理选择规则

  1. 默认优先:目标类实现接口 → 选用JDK动态代理;
  2. 兜底方案:目标类无接口 → 选用CGLIB动态代理;
  3. 强制配置:通过spring.aop.proxy-target-class=true,可强制所有场景使用CGLIB。

三、Spring AOP核心组件与职责

Spring AOP通过一套标准化组件封装代理逻辑,底层组件协同完成代理生成、方法拦截、逻辑织入,核心组件如下:

  • Advisor:切面的最小封装单元,组合了Pointcut(切点)和Advice(通知),决定“拦截谁”+“做什么”。
  • ProxyFactory:代理生成工厂,统一封装JDK/CGLIB代理的创建逻辑,屏蔽两种代理的差异。
  • MethodInterceptor:方法拦截器,所有通知最终都会被封装为拦截器,是横切逻辑的实际执行者。
  • BeanPostProcessor:Spring bean后置处理器,AOP的入口,在bean初始化完成后,判断是否需要生成代理对象,替换原始bean。
  • AopProxy:代理对象顶层接口,JdkDynamicAopProxy、CglibAopProxy是其实现类,分别对应两种代理方案。

四、完整底层执行流程

Spring AOP的全流程分为代理生成方法调用拦截两个阶段,全程在Spring容器启动+bean初始化阶段完成。

阶段1:容器启动,解析切面

  1. Spring扫描所有标注@Aspect的切面类,解析切点表达式、通知类型;
  2. 将切面封装为Advisor对象,存入容器的切面缓存中。

阶段2:Bean初始化,生成代理

  1. 目标bean执行初始化方法后,进入AnnotationAwareAspectJAutoProxyCreator(AOP核心后置处理器);
  2. 后置处理器遍历所有Advisor,匹配目标bean的方法是否符合切点规则;
  3. 若匹配成功,通过ProxyFactory选择JDK/CGLIB方案,生成代理对象;
  4. 用代理对象替换容器中的原始目标bean,后续调用bean时,实际调用的是代理对象。

阶段3:方法调用,执行织入逻辑

  1. 调用代理对象的目标方法时,触发拦截器链(所有匹配的MethodInterceptor);
  2. 按通知顺序执行横切逻辑:环绕通知(before逻辑) → 前置通知 → 目标方法执行 → 环绕通知(after逻辑) → 返回通知 → 后置通知;
  3. 环绕通知作为核心拦截器,可控制目标方法的执行时机、参数修改、返回值增强。

五、源码关键链路(简化版)

贴合底层源码,梳理核心调用链,直击AOP实现本质:

  1. 入口:AbstractAutoProxyCreator.postProcessAfterInitialization()(bean初始化后处理)
  2. 匹配切点:getAdvicesAndAdvisorsForBean()(筛选匹配的切面)
  3. 创建代理:createProxy() → ProxyFactory.getProxy()
  4. JDK代理执行:JdkDynamicAopProxy.invoke() → 拦截器链调用
  5. CGLIB代理执行:CglibAopProxy.DynamicAdvisedInterceptor.intercept() → 拦截器链调用

六、关键误区澄清

  • Spring AOP≠AspectJ:Spring AOP是运行时动态代理,轻量无侵入;AspectJ是编译期/类加载期织入,功能更强大但需编译增强。
  • 同类方法调用不生效:Spring AOP基于代理对象,同类内部方法调用(this.方法)会绕过代理,无法触发切面,需通过自我注入或暴露代理对象解决。
  • 性能差异:JDK代理基于反射,CGLIB基于字节码,单次调用CGLIB略快,但代理生成阶段CGLIB耗时更长,Spring的默认选择已做最优平衡。

总结:Spring AOP底层就是动态代理+拦截器链,通过运行时生成代理对象接管方法调用,将横切逻辑与业务逻辑彻底分离,是Spring事务、缓存等核心功能的底层支撑。

ps:如果这篇帖子对于还在找工作和找实习的你有所帮助,可以关注我,给本贴点赞、评论、收藏并订阅专栏;同时不要吝啬您的花花

Spring 文章被收录于专栏

本专栏聚焦Spring全生态体系,从IoC/AOP核心原理入手,覆盖Spring Boot自动配置、事务管理、Web开发等实战内容。拆解循环依赖、动态代理等高频面试难点,助力开发者从入门到精通,打通单体到微服务的技术链路,解决企业级开发痛点,提升架构设计与问题排查能力,成为Java后端进阶的必备技术专栏。

全部评论

相关推荐

评论
点赞
收藏
分享

创作者周榜

更多
正在热议
更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务