ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Spring AOP原理与实践详解

Spring AOP原理与实践详解 Spring AOP原理与实践详解定位讲透 AOP 概念体系、两种动态代理、切点表达式、织入时机、事务与 AOP 的关系及自调用等失效场景适用版本Spring Framework 6.xJDK 17目录一、AOP 概念体系二、动态代理实现三、切点表达式四、织入时机与代理生命周期五、事务与 AOP六、实践要点七、总结八、常见高频面试题一、AOP 概念体系1.1 AOP 解决什么问题横切关注点日志、事务、权限、监控、限流与业务逻辑交织若内联在每个方法里会大量重复、污染业务。AOP 把这些逻辑抽成切面由框架在运行期织入目标方法业务代码保持纯净。无 AOP每个方法开头结尾重复写 开事务/记日志/权限检查 有 AOP业务方法只写业务切面统一织入横切逻辑1.2 核心术语术语含义Aspect切面切点 通知的封装一个 Aspect 类Join Point连接点可被拦截的执行点。Spring AOP 只支持方法执行区别于 AspectJ 的字段/构造器Pointcut切点用表达式匹配哪些连接点被拦截Advice通知拦截后执行的具体逻辑Weaving织入把切面应用到目标对象的过程Spring 在运行期通过代理Target / Proxy目标对象 / 生成的代理对象1.3 五种通知类型Around 包裹整个调用 Before 目标方法执行前 → 目标方法执行 AfterReturning 正常返回后 AfterThrowing 抛异常后 After 无论结果都执行finally 语义通知时机典型用途Before执行前参数校验、权限After结束后无论成败清理资源AfterReturning正常返回后结果加工/记录AfterThrowing抛异常后异常日志Around环绕控制是否调用目标事务、耗时统计、缓存Around最强大通过ProceedingJoinPoint#proceed()决定是否、何时调用目标可改写参数与返回值。二、动态代理实现Spring AOP 是运行期代理区别于编译期/加载期织入的 AspectJ容器为目标对象生成代理调用先进代理代理执行通知后转发目标。2.1 两种代理方式维度JDK 动态代理CGLIB机制基于接口ProxyInvocationHandler生成目标类的子类字节码增强要求目标必须实现接口目标类与方法不能是 final产物实现相同接口的代理类目标类的子类性能创建快、调用经反射创建慢、调用快方法快调用2.2 选择规则目标实现了接口 → 默认 JDK 动态代理 目标无接口 → 使用 CGLIB 配置强制 → proxyTargetClass true一律 CGLIB Spring Boot 2.x 起默认 proxyTargetClass true统一 CGLIB实践影响JDK 代理下instanceof 具体类为 false代理只实现接口——这是类型判断失效类 bug 的根源CGLIB 不能代理 final 类/方法、私有方法子类无法覆盖面向接口编程可避免两种代理的差异暴露给业务。三、切点表达式3.1 execution 指示符最常用execution(修饰符? 返回类型 包.类.方法(参数)) 示例 execution(public * com.demo.service.*.*(..)) // service 包下所有类的所有 public 方法 execution(* com.demo..*.*(..)) // 包及子包所有方法 execution(* com.demo.service.UserService.getById(Long)) // 精确到方法..表示任意层级包/任意参数*表示任意单个。3.2 其他常用指示符指示符匹配within(类)指定类/包内的方法annotation(注解)带某注解的方法最常用如自定义 Logwithin(注解)带某注解的类args(...)参数类型匹配this/target代理对象 / 目标对象类型annotation是工程里最推荐的方式自定义注解标在方法上切点匹配注解——精确、可读、易维护避免包路径通配误伤。3.3 组合Pointcut(execution(...) annotation(com.demo.Log)) 复合 与、|| 或、! 非四、织入时机与代理生命周期4.1 代理在生命周期的哪一步产生回接 01 篇代理在BeanPostProcessor 后置处理生命周期第 6 步生成。EnableAspectJAutoProxyBoot 自动开启注册AnnotationAwareAspectJAutoProxyCreator——它就是一个 BeanPostProcessor扫描 Aspect 切面对匹配切点的 Bean 返回代理。4.2 与循环依赖的交互02 篇三级缓存若被代理的 Bean 涉及循环依赖代理会在提前暴露时三级缓存工厂提前生成保证注入方拿到的是代理而非原始对象。4.3 容器里存的是代理getBean与注入拿到的都是代理对象调用任何方法都先经过通知链——理解这一点才能解释自调用失效第五节。五、事务与 AOP5.1 Transactional 的本质声明式事务是 AOP 最典型的应用Transactional触发事务拦截器织入方法调用被代理包裹成开事务 → 执行 → 提交/回滚。理解了 AOP就理解了事务的一切失效场景。5.2 失效场景汇总场景原因解法同类自调用内部调用走this绕过代理拆分到另一 Bean / 注入自身 / AopContext方法非 public代理默认只拦 public改为 public异常被 catch 吞掉无异常抛出不触发回滚抛出或手动标记回滚抛受检异常但默认只回滚运行时异常默认规则只回滚 RuntimeException/Error配置rollbackFor Exception.class静态/final 方法代理无法拦截改造设计数据库引擎不支持事务MyISAM 等换 InnoDB5.3 自调用问题的三种解法① 拆分把被调用方法移到另一个 Bean最干净 ② 自注入注入自身代理后调用注意构造器注入会循环用字段/Lazy ③ AopContext.currentProxy()从上下文取当前代理 调用 ((XxxService) AopContext.currentProxy()).method() 需 exposeProxy true六、实践要点要点说明通知顺序多个切面拦截同一方法时用Order控制先后切面轻量通知里不放重逻辑尤其 Around 全链路避免拖慢所有目标参数获取JoinPoint取参数/目标Around 用ProceedingJoinPoint匹配收紧切点表达式精确到注解或具体包避免全量拦截影响性能异常处理Around 中捕获异常要决定是否重新抛出否则改变异常语义七、总结AOP 分离横切关注点Spring AOP 只支持方法级连接点运行期通过代理织入核心术语切面/连接点/切点/通知/织入要分清。五种通知Around 最强大控制是否执行目标After 是 finally 语义。两种代理JDK 基于接口、CGLIB 生成子类Boot 2.x 起默认 CGLIBJDK 代理下 instanceof 具体类会失败。切点execution 通配annotation 最推荐自定义注解驱动精确可维护。织入时机Bean 生命周期 BPP 后置阶段EnableAspectJAutoProxy 注册的创建器即 BeanPostProcessor循环依赖时三级缓存可提前生成代理。事务即 AOPTransactional 失效场景全部源于代理机制——自调用、非 public、异常吞掉、受检异常默认不回滚解法是拆 Bean、自注入或 AopContext。八、常见高频面试题1. Spring AOP 和 AspectJ 的区别要点织入时机不同。Spring AOP 是运行期代理在容器创建 Bean 时生成代理织入只支持方法执行连接点无需特殊编译器适合 Spring 生态内的横切AspectJ 是编译期/类加载期字节码织入支持字段、构造器等更丰富连接点功能更强但需专门工具链。Spring 项目默认用 Spring AOP 即够需要字段级或极致性能才上 AspectJ。2. JDK 动态代理和 CGLIB 的区别Spring 怎么选要点JDK 基于接口Proxy InvocationHandler要求目标实现接口生成实现同接口的代理类CGLIB 生成目标类的子类字节码增强目标类/方法不能 final创建较慢但调用较快。选择有接口默认 JDK无接口用 CGLIBSpring Boot 2.x 起默认 proxyTargetClasstrue 一律 CGLIB。JDK 代理下代理只实现接口instanceof 具体类为 false。3. Spring AOP 的织入发生在 Bean 生命周期的哪一步要点在 BeanPostProcessor 后置处理阶段生命周期第 6 步初始化之后。EnableAspectJAutoProxy 注册 AnnotationAwareAspectJAutoProxyCreator本身是 BeanPostProcessor它扫描所有 Aspect 切面对匹配切点的 Bean 在后置处理时返回代理对象。若 Bean 涉及循环依赖代理可能在三级缓存提前暴露时提前生成。4. 五种通知类型的执行顺序要点正常流程Around前半→ Before → 目标方法 → AfterReturning → After → Around后半异常流程Around前半→ Before → 目标方法抛异常 → AfterThrowing → After → Around 捕获。After 无论成败都执行finally 语义。多个切面时按 Order 排序Around 最灵活可控制是否调用 proceed()。5. 什么是 AOP 的自调用失效有哪些解法要点同类中方法 A 调用方法 B若通过 this 调用则绕过代理对象B 上的切面/事务不生效——因为通知只织入在代理上内部直调不经过代理。解法① 把 B 拆到另一个 Bean最干净② 注入自身代理再调用自注入注意避免构造器循环③ 开启 exposeProxy 后用 AopContext.currentProxy() 获取当前代理调用。事务自调用失效是其中最著名的案例。6. Transactional 失效的常见场景有哪些要点① 同类自调用绕过代理② 方法非 public③ 异常被 catch 吞掉未抛出④ 抛出受检异常但默认只回滚 RuntimeException/Error需配 rollbackForException.class⑤ 静态或 final 方法代理拦不到⑥ 数据库引擎不支持事务MyISAM。根本原因都指向代理机制与回滚规则理解 AOP 即可系统记忆。7. Pointcut 里 annotation 和 execution 怎么选要点execution 按包/类/方法签名通配适合对整个分层如 service 包统一织入annotation 匹配带特定注解的方法适合精确控制——自定义注解标在需要的方法上可读性、可维护性、精确度都更好避免包路径通配误伤。工程实践推荐 annotation 驱动大范围统一逻辑全 service 层日志用 execution。两者可用 组合。8. Around 通知能做什么其他通知做不到的事要点Around 通过 ProceedingJoinPoint 完整控制目标方法的执行——可以决定是否调用 proceed()短路如缓存命中直接返回、改写入参、捕获并处理返回值或异常、统计精确耗时。其他通知只能在固定时机插入逻辑无法控制目标是否执行。事务、缓存、限流这类包裹式逻辑都依赖 Around。9. 多个切面拦截同一个方法顺序怎么控制要点用 Order或实现 Ordered标注切面类值越小优先级越高。同一连接点上进入顺序按 Order 从小到大返回顺序相反洋葱模型。若切面间有逻辑依赖如事务必须在某个重试切面之内必须显式控制顺序而不是依赖默认。10. 为什么说 Spring AOP 是运行期代理这带来什么限制要点Spring 不修改目标类字节码而是在容器创建 Bean 时生成代理对象调用经代理转发——所以只有从容器获取并经代理调用的路径才有切面内部 this 直调、new 出来的对象、static 方法都被绕过。限制还包括只支持方法连接点、依赖代理机制的性能开销。这是理解所有 AOP/事务失效场景的总根源。
返回列表