资讯动态

《Java 100 天进阶之路》第75篇:Spring AOP原理(2026版)

发布时间:2026/8/18 6:34:05 来源:尧图企业网站定制
第75篇Spring AOP原理2026精版系列导航《Java 100 天进阶之路》完整目录 |⬅️ 上一篇第74篇Bean生命周期 |➡️ 下一篇第76篇Spring事务待发布️ 本文阅读地图3 分钟速览第74篇搞定了Bean生命周期本篇深入Spring AOP原理。AOP是Spring框架的另一大核心Transactional、Async、权限校验、日志记录等功能的底层都是AOP。理解了AOP就理解了Spring的增强能力从何而来模块核心问题一句话回答AOP是什么面向切面编程解决什么问题把日志、事务等横切关注点从业务代码中抽离出来集中管理核心概念切面、切点、通知、连接点是什么切面是功能模块切点定义在哪里执行通知定义做什么代理机制AOP底层怎么实现的动态代理——JDK动态代理基于接口或CGLIB基于继承JDK vs CGLIB两种代理怎么选目标类有接口用JDK无接口用CGLIBSpring Boot 2.x默认CGLIB执行顺序多个通知的执行顺序Around(前) →Before→ 目标方法 →Around(后) →After→AfterReturning/AfterThrowingAOP失效场景为什么Transactional有时不生效自调用、非public方法、异常被吞掉等面试最爱问高频考点有哪些见文末 面试小节一、核心知识点1. AOP是什么AOPAspect-Oriented Programming面向切面编程是一种编程范式旨在将横切关注点cross-cutting concerns与核心业务逻辑分离。横切关注点是指那些跨越多个业务模块的通用功能——日志记录、事务管理、权限控制、性能监控等。AOP通过代理模式实现了对这些功能的集中管理使核心业务逻辑与通用功能解耦。OOP vs AOPOOP按纵向维度继承、封装、多态组织代码适合描述是什么AOP按横向维度切面组织代码适合描述在什么时机做什么事。两者互补不是替代关系。2. AOP解决了什么问题问题传统方式AOP方式代码重复每个业务方法都要写日志、事务代码切面集中定义一处修改处处生效耦合度高业务代码混杂着非业务逻辑业务类只关注核心逻辑干净纯粹难以维护改一个日志格式要改几百个文件只改切面类即可难以扩展新增功能要改所有相关类新增切面类无侵入式扩展二、通俗讲解1分钟开心学把AOP想象成餐厅的中央厨房核心业务OOP餐厅的菜谱——宫保鸡丁怎么做、水煮鱼怎么做每道菜有自己独特的做法纵向。横切关注点AOP餐厅的后勤管理——所有菜都要洗菜前置处理、装盘后置处理、记录每道菜耗时环绕处理。这些事跟菜本身的做法无关但每道菜都要做。传统方式每个厨师做菜时都要自己洗菜、装盘、记时间——代码重复改起来麻烦。AOP方式专门配一个后勤团队切面统一负责洗菜、装盘、记时间。厨师只管炒菜。哪天要换装盘方式只改后勤团队所有菜自动生效。AOP的核心比喻切面Aspect 后勤团队日志团队、事务团队、权限团队切点Pointcut 规则所有热菜都要洗菜匹配哪些方法通知Advice 具体动作洗菜在切点执行的代码连接点Join Point 每一次洗菜的具体时机方法执行的某个时刻织入Weaving 把后勤团队嵌入到厨师做菜流程中的过程三、AOP核心概念详解3.1 六大核心概念Spring AOP建立在几个关键抽象之上概念英文通俗理解示例连接点Join Point程序执行过程中可以插入切面逻辑的位置如方法调用、异常抛出任何Service方法调用切点Pointcut通过表达式匹配一组连接点决定在哪里执行execution(* com.example.service.*.*(..))通知Advice在特定连接点执行的具体动作决定做什么Before记录日志、Around开启事务切面Aspect封装横切关注点的模块包含通知和切点Aspect标注的LogAspect类目标对象Target Object被代理的原始业务对象UserServiceImpl实例织入Weaving将切面代码与目标对象关联的过程Spring在运行时通过动态代理完成Spring AOP的限制Spring AOP仅支持方法级别的连接点——只能在方法调用时插入切面逻辑不支持字段访问、构造器调用等更细粒度的连接点。如果需要更强大的AOP能力需要配合AspectJ使用。3.2 五种通知类型Spring AOP提供了五种通知类型在不同的时机插入横切逻辑注解类型执行时机典型场景Before前置通知目标方法执行前参数校验、权限检查After后置通知目标方法执行后无论是否异常类似finally资源清理、释放连接AfterReturning返回后通知目标方法正常返回后记录返回值、缓存更新AfterThrowing异常通知目标方法抛出异常后异常监控、告警Around环绕通知包裹目标方法控制其执行流程事务管理、性能监控、日志记录Around最强大它可以完全控制目标方法的执行——在方法执行前后插入逻辑甚至可以决定是否执行目标方法。Transactional的底层实现就是Around通知。四、AOP底层原理动态代理4.1 代理模式核心思想Spring AOP的实现本质上依赖于代理模式。代理模式通过引入代理对象作为目标对象的中间层实现了对目标对象访问的控制与增强。Spring AOP在运行时通过动态代理将切面代码织入目标对象。核心理解AOP的底层就是动态代理。容器中放的不是原始对象而是代理对象。你调用userService.saveUser()时实际调用的是代理对象的方法——代理对象先执行切面逻辑事务开启、日志记录再调用原始对象的方法。4.2 JDK动态代理 vs CGLIBSpring AOP使用两种方式创建动态代理对比维度JDK动态代理CGLIB动态代理实现方式基于接口生成代理类通过继承目标类生成子类使用条件目标类实现了至少一个接口目标类没有实现接口或强制指定核心类java.lang.reflect.ProxyInvocationHandlernet.sf.cglib.proxy.Enhancer方法调用通过反射调用invoke()通过方法拦截器调用性能更高限制只能代理接口中定义的方法final类无法代理final/private方法无法增强JDK动态代理实现示例// 1. 定义接口publicinterfaceUserService{voidsaveUser(Useruser);}// 2. 实现类publicclassUserServiceImplimplementsUserService{publicvoidsaveUser(Useruser){// 业务逻辑}}// 3. 实现InvocationHandlerpublicclassLogInvocationHandlerimplementsInvocationHandler{privateObjecttarget;publicLogInvocationHandler(Objecttarget){this.targettarget;}OverridepublicObjectinvoke(Objectproxy,Methodmethod,Object[]args)throwsThrowable{System.out.println(【前置】方法method.getName() 开始执行);Objectresultmethod.invoke(target,args);System.out.println(【后置】方法method.getName() 执行完成);returnresult;}}// 4. 创建代理UserServicetargetnewUserServiceImpl();InvocationHandlerhandlernewLogInvocationHandler(target);UserServiceproxy(UserService)Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),handler);proxy.saveUser(newUser());4.3 Spring如何选择代理方式Spring的代理选择策略场景代理方式目标类实现了接口JDK动态代理默认目标类未实现接口CGLIB动态代理强制使用CGLIB设置EnableAspectJAutoProxy(proxyTargetClass true)Spring Boot的默认行为Spring Boot从2.0开始默认将spring.aop.proxy-target-class设置为true即优先使用CGLIB代理。这意味着即使你的类实现了接口Spring Boot也会用CGLIB来代理这样可以代理类中所有方法而不仅仅是接口中定义的方法。强制使用CGLIB的配置方式ConfigurationEnableAspectJAutoProxy(proxyTargetClasstrue)// 强制CGLIBpublicclassAppConfig{}# application.ymlspring:aop:proxy-target-class:true# Spring Boot 2.x 默认就是true4.4 Spring AOP vs AspectJ很多开发者容易混淆Spring AOP和AspectJ两者有本质区别对比维度Spring AOPAspectJ织入时机运行时动态代理编译时或类加载时织入实现方式生成代理对象直接修改字节码连接点支持仅方法调用字段访问、构造器、静态代码块等性能略低代理调用链更高直接执行增强后的字节码使用复杂度简单与Spring无缝集成需要AspectJ编译器ajc适用场景轻量级、Spring Bean的方法增强复杂AOP需求、非Spring管理的对象一句话总结Spring AOP是运行时套壳子AspectJ是编译时改代码。大多数场景下Spring AOP足够用只有在需要代理非Spring Bean或需要字段级别拦截时才需要AspectJ。五、AOP代理创建流程源码级5.1 三大核心步骤Spring AOP代理对象的创建分为三个核心步骤5.2 源码核心类解析Spring AOP的代理创建由AbstractAutoProxyCreator驱动它是BeanPostProcessor的一个实现在Bean生命周期中发挥作用。核心类继承关系BeanPostProcessorBean生命周期扩展点 ↑ 实现 AbstractAutoProxyCreator自动代理创建器基类 ↑ 继承 AbstractAdvisorAutoProxyCreator增加Advisor处理能力 ↑ 继承 AnnotationAwareAspectJAutoProxyCreatorAspectJ注解驱动实际使用的实现类核心方法postProcessBeforeInstantiationOverridepublicObjectpostProcessBeforeInstantiation(Class?beanClass,StringbeanName){// 1. 检查是否应该跳过if(isInfrastructureClass(beanClass)||shouldSkip(beanClass,beanName)){this.advisedBeans.put(cacheKey,Boolean.FALSE);returnnull;}// 2. 获取自定义TargetSourceTargetSourcetargetSourcegetCustomTargetSource(beanClass,beanName);if(targetSource!null){// 3. 获取适用于该Bean的增强器Object[]specificInterceptorsgetAdvicesAndAdvisorsForBean(beanClass,beanName,targetSource);// 4. 创建代理对象returncreateProxy(beanClass,beanName,specificInterceptors,targetSource);}returnnull;}代理创建流程BeanPostProcessor机制在Bean实例化之前AbstractAutoProxyCreator拦截Bean的创建过程解析切面定义扫描所有Aspect切面类构建Advisor集合匹配增强器通过切点表达式判断当前Bean是否需要被代理创建ProxyFactory将目标对象、增强器等信息注入代理工厂选择代理策略根据目标类是否实现接口选择JDK或CGLIB生成代理对象通过ProxyFactory创建代理对象替换容器中的原始Bean六、AOP执行顺序6.1 单个切面中五种通知的执行顺序当一个切面中定义了多种通知时执行顺序如下代码示例AspectComponentpublicclassLogAspect{Around(execution(* com.example.service.*.*(..)))publicObjectaround(ProceedingJoinPointpjp)throwsThrowable{System.out.println(① Around 前置方法开始);Objectresultpjp.proceed();// 执行目标方法System.out.println(④ Around 后置方法结束);returnresult;}Before(execution(* com.example.service.*.*(..)))publicvoidbefore(){System.out.println(② Before前置通知);}After(execution(* com.example.service.*.*(..)))publicvoidafter(){System.out.println(⑤ After后置通知finally);}AfterReturning(execution(* com.example.service.*.*(..)))publicvoidafterReturning(){System.out.println(⑥ AfterReturning正常返回);}}执行结果① Around 前置方法开始 ② Before前置通知 —— 目标方法执行 —— ④ Around 后置方法结束 ⑤ After后置通知finally ⑥ AfterReturning正常返回6.2 多个切面的执行顺序当多个切面作用于同一个目标方法时通过Order注解控制执行顺序规则说明Order(数值)数值越小优先级越高优先级高的切面外层先执行前置后执行后置优先级低的切面内层后执行前置先执行后置多个切面嵌套执行顺序切面AOrder(1)外层Around前置 ↓ 切面BOrder(2)内层Around前置 ↓ 切面B Before ↓ 目标方法执行 ↓ 切面B Around后置 ↓ 切面B After ↓ 切面A Around后置 ↓ 切面A After记忆口诀前置通知数值小先执行后置通知数值大先执行——整体像一个栈先进后出。七、AOP失效场景高频面试题AOP基于动态代理实现以下场景会导致AOP失效7.1 自调用最经典的失效场景问题代码ServicepublicclassUserService{Transactional// ❌ 不会生效publicvoidupdateUser(Useruser){// 更新用户逻辑}publicvoidupdateUserWithLog(Useruser){// 直接调用本类方法——绕过了代理对象this.updateUser(user);// ❌ AOP失效logService.saveLog(更新用户);}}为什么失效this.updateUser()调用的是原始对象的方法而不是代理对象的方法。Spring的AOP增强是通过代理对象实现的——只有通过代理对象调用方法切面逻辑才会执行。解决方案ServicepublicclassUserService{AutowiredprivateUserServiceself;// 注入自身代理对象TransactionalpublicvoidupdateUser(Useruser){// 更新用户逻辑}publicvoidupdateUserWithLog(Useruser){self.updateUser(user);// ✅ 通过代理对象调用事务生效}}或者使用AopContext.currentProxy()((UserService)AopContext.currentProxy()).updateUser(user);7.2 其他常见失效场景失效场景原因解决方案非public方法代理只能增强public方法改为public异常被try-catch吞掉通知无法捕获异常在catch中重新抛出异常内部方法调用自调用绕过代理对象通过代理对象调用或拆分到不同类方法被final修饰CGLIB无法重写final方法移除final修饰类被final修饰CGLIB无法继承final类移除final修饰八、避坑要点错误/误区后果正确做法认为AOP能代理所有方法private/final方法增强失败只对public方法使用AOP在类内部调用自己的AOP方法切面逻辑不执行自调用通过代理对象调用或拆分到不同类混淆Spring AOP和AspectJ对AOP能力预期错误Spring AOP仅支持方法级别运行时织入忘记加EnableAspectJAutoProxyAspect不生效Spring Boot自动配置无需手动添加在Around中忘记调用proceed()目标方法不执行必须在Around中调用pjp.proceed()认为JDK代理比CGLIB快选错代理方式JDK代理有反射开销CGLIB性能更好九、面试高频考点Q1AOP的底层原理是什么AOP的底层是动态代理。Spring在运行时为目标Bean创建代理对象JDK动态代理或CGLIB将切面逻辑织入代理对象中。当调用目标方法时实际调用的是代理对象的方法——代理对象先执行切面逻辑通知再调用原始对象的方法。Q2JDK动态代理和CGLIB有什么区别JDK动态代理基于接口要求目标类实现至少一个接口通过Proxy和InvocationHandler生成代理。CGLIB基于继承通过生成目标类的子类来实现代理不需要接口。JDK只能代理接口中定义的方法CGLIB可以代理类中所有非final方法。Spring Boot 2.x默认使用CGLIB。Q3Transactional为什么会失效最经典的是自调用失效——同一个类中一个非事务方法调用本类的Transactional方法调用的是原始对象的this引用绕过了代理对象事务不生效。此外private方法、final方法、异常被try-catch吞掉、未启用事务管理也会导致失效。Q4Around和BeforeAfter有什么区别Around是环绕通知可以完全控制目标方法的执行——决定是否执行、何时执行、执行前后做什么。BeforeAfter只能分别在方法前后执行逻辑无法控制方法是否执行。Transactional的底层就是Around通知——开启事务→执行业务方法→根据结果提交或回滚。Q5Spring AOP和AspectJ有什么区别Spring AOP是运行时通过动态代理织入仅支持方法级别的连接点。AspectJ是编译时或类加载时通过修改字节码织入支持字段、构造器、静态代码块等更丰富的连接点。Spring AOP更简单易用AspectJ功能更强大、性能更好。 面试官追问陷阱加分题追问1“CGLIB能代理final类吗能代理final方法吗”都不能。CGLIB通过继承目标类生成子类来实现代理——final类不能被继承final方法不能被重写。所以如果你的类或方法被final修饰AOP增强会失效。Spring会fallback到JDK动态代理吗不会——如果类没有接口且是final的代理创建会直接失败。追问2“Spring Boot默认用CGLIB那如果一个类既实现了接口又被final修饰了会怎样” 这是个好问题。Spring Boot默认CGLIB但final类无法被CGLIB代理。此时Spring会自动降级到JDK动态代理——只要该类实现了至少一个接口。所以结果是代理可以创建成功但使用的是JDK方式。这说明Spring的代理选择策略是有容错机制的——CGLIB不行就换JDK。追问3“Around通知中调用了pjp.proceed()两次会怎样” 目标方法会被执行两次。这在某些场景下是故意的比如重试机制但大多数情况下是bug。每次proceed()都会重新执行目标方法及其嵌套的切面逻辑。所以如果你在Around中不小心写了两次proceed()事务可能会被开启两次、日志会记录两遍。十、练习题简答题JDK动态代理和CGLIB动态代理在实现原理上有什么本质区别各自的使用条件是什么代码题编写一个Around环绕通知统计目标方法的执行时间并在方法执行前后打印日志。分析题某项目中Transactional注解在UserService.updateUser()方法上不生效但OrderService.createOrder()方法上的Transactional正常。updateUser()是被UserService内部的另一个方法调用的。请分析原因并给出解决方案。 你的学习进度当前第75篇 / 共108篇 ·进阶篇Spring全家桶第73~82篇✅ 已完成基础篇44篇 第45~75篇 正在学第75篇⏳ 待学习第76~108篇 完整目录 学习指南 | 订阅本专栏不错过每一篇 下一篇文章预告下一篇《第76篇Spring事务》内容简介声明式事务Transactional原理、事务传播行为REQUIRED/REQUIRES_NEW/NESTED等7种、隔离级别、事务失效场景深度剖析、编程式事务。Spring全家桶专题持续推进拿下事务管理《Java 100 天进阶之路 | 从入门到上岗就业》每天一篇建议收藏 关注一起100天拿offer 点击关注我更新后第一时间收到推送

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价