Spring面试题 - AOP总结

1. AOP概念与基础

与 IOC 一样,咱先回顾一下 AOP 中涉及到的概念。

1.1 AOP概述

AOP 面向切面编程,全称 Aspect Oriented Programming ,它是 OOP 的补充。OOP 关注的核心是对象,AOP 的核心是切面(Aspect)。AOP 可以在不修改功能代码本身的前提下,使用运行时动态代理的技术对已有代码逻辑增强。AOP 可以实现组件化、可插拔式的功能扩展,通过简单配置即可将功能增强到指定的切入点。

学完整个 AOP 部分后,是不是概述中的这些概念就更容易理解和接受了呢?所以咱一开始说这些概念不好理解没关系,随着学习的深入,小伙伴们自然会体会到 “拨开云雾见光明” 的感觉。

1.2 AOP的设计原理

如果只是从咱最常见的角度出发的话,那这个问题可以这样回答:

AOP的底层设计是由运行时动态代理支撑,在 bean 的初始化流程中,借助 BeanPostProcessor 将原始的目标对象织入通知,生成代理对象。

但是咱通过 AOP 高级中了解到的知识,可以了解到 AOP 的通知织入分为三种情况,所以上面的回答只回答了一种情况,另外两种也可以提一下,就像这样:

AOP 的设计原理是对原有业务逻辑的横切增强,使用不同的通知织入方式,它有不同的底层原理支撑(编译期、类加载器、对象创建期)。

1.3 jdk动态代理和Cglib动态代理的对比

  • jdk 动态代理要求被代理的对象所属类至少实现一个接口,它是 jdk 内置的机制
  • Cglib 动态代理无此限制,使用字节码增强技术实现,需要依赖第三方 Cglib 包
  • jdk 动态代理的代理对象创建速度快,执行速度慢;Cglib 动态代理的代理对象创建速度慢,执行速度快

1.4 理解AOP的术语

之前咱借助表情包的场景理解了 AOP 的术语,这里咱再回顾一下吧。

  • Target 目标对象:被代理的原始对象
  • Proxy 代理对象:目标对象被织入通知后的产物就是代理对象
  • JoinPoint 连接点:目标对象的所属类中,定义的所有方法均为连接点
  • Pointcut 切入点:被切面拦截 / 增强的连接点(切入点一定是连接点,连接点不一定是切入点)
  • Advice 通知:增强的逻辑 / 代码,也即拦截到目标对象的连接点之后要做的事情
  • Aspect 切面:切入点 + 通知
  • Weaving 织入:将通知应用到目标对象,进而生成代理对象的过程动作
  • Introduction 引介:特殊的通知类型,可以在不修改原有类的代码的前提下,在运行期为原始类动态添加新的属性 / 方法

1.5 通知的类型

注意,这里说的通知类型并没有指具体谁的,AOP 联盟定义的通知类型,与 AspectJ 定义的通知类型是不一样的,小伙伴们注意区分。

1.5.1 AspectJ定义的通知类型

  • Before 前置通知:目标对象的方法调用之前触发
  • After 后置通知:目标对象的方法调用之后触发
  • AfterReturning 返回通知:目标对象的方法调用完成,在返回结果值之后触发
  • AfterThrowing 异常通知:目标对象的方法运行中抛出 / 触发异常后触发
    • 注意一点,AfterReturning 与 AfterThrowing 两者是互斥的!如果方法调用成功无异常,则会有返回值;如果方法抛出了异常,则不会有返回值。
  • Around 环绕通知:编程式控制目标对象的方法调用。环绕通知是所有通知类型中可操作范围最大的一种,因为它可以直接拿到目标对象,以及要执行的方法,所以环绕通知可以任意的在目标对象的方法调用前后搞事,甚至不调用目标对象的方法

1.5.2 AOP联盟定义的通知类型

  • 前置通知 MethodBeforeAdvice
  • 后置通知(返回通知)AfterReturningAdvice
  • 异常通知 ThrowsAdvice
  • 环绕通知 MethodInterceptor
  • 引介通知 IntroductionAdvisor

2. AOP的编码与应用

2.1 AOP的使用场景

AOP 的使用范围还是非常广的,前面小册里咱只提了日志和事务,其实还是有蛮多的,咱这里可以简单列一下:

  • 业务日志切面:可以记录业务方法调用的痕迹
  • 事务控制:通过切面可以声明式控制事务
  • 权限校验:执行方法之前先校验当前登录用户是否有权调用
  • 数据缓存:执行方法之前先从缓存中取,取到则直接返回不走业务方法
  • 。。。。。。

2.2 多个切面的执行顺序如何控制

  • 显式使用 @Order 注解,或者 Ordered 接口,声明切面的执行顺序(默认 Integer.MAX_VALUE )
  • 通过使用类名的 unicode 编码顺序,控制切面的执行顺序

2.3 AOP的失效场景

对于这个失效的场景,由于还没有学到 SpringWebMvc 部分,所以可以解释的内容相对有限。不过考虑到可能有些小伙伴看小册是为了查缺补漏,或者备战面试,咱这里还是把常见的可能出现的几种情况都列出来。

  • 代理对象调用自身的方法时,AOP 通知会失效
    • 即在代理对象中直接调用 this.xxx 方法
    • 正确做法是借助 AopContext 取到当前代理对象并强转,之后调用,这样 AOP 通知依然会执行
  • 代理对象在后置处理器还没有初始化的时候,提前创建了,则 AOP 通知不会织入
    • 由于 AOP 是借助 BeanPostProcessor 实现,如果 BeanPostProcessor 还没有初始化好,目标对象已经创建了,则不可能再生成代理对象
  • WebMvc :原始的 SSM 工程架构中,AOP 配置的切入点表达式,切入的是一些 Controller ,导致通知失效
    • 传统的 SSM 项目中,SpringFramework 容器与 SpringWebMvc 容器是父子容器,如果在父容器中定义切面类,则 MVC 的子容器无法感知到父容器中的通知,也就没办法织入通知了
    • 正确做法是将切面类注册到 spring-mvc.xml 中,并开启 AspectJ 注解 AOP
  • WebMvc :Controller 依赖的 Service 同时被父容器和子容器扫描,则 Service 的通知会失效
    • 如果父容器和子容器都扫描到 Service ,则父容器和子容器中都会有一个 Service 的 bean 对象,这样在 Controller 依赖注入时,会直接拿 MVC 子容器中没有经过 AOP 代理的 Service ,而不会去父容器拿经过了 AOP 代理的 Service
    • 正确做法是避免两个包扫描的部分产生交集

3. AOP的高级应用

3.1 AOP的底层原理机制

AOP 在底层,借助 AnnotationAwareAspectJAutoProxyCreator 在 bean 的初始化流程,postProcessAfterInitialization 方法中将目标对象包装为代理对象。这里面涉及到几个核心步骤:

  1. 检查当前初始化的 bean 是否可以被 AOP 代理(检查是否有匹配的增强器)
  2. 如果存在,则根据当前初始化的 bean 所属类有无实现接口,以及 AOP 的全局配置,决定使用哪种代理方案
  3. 将目标对象包装为 TargetSource ,并以此为原型生成代理对象

3.2 代理对象的通知逻辑是如何执行的

代理对象被构造后,执行方法会进入 JdkDynamicAopProxy / CglibAopProxy 中,并构造 ReflectiveMethodInvocation 并依次执行这些织入的通知。执行通知的逻辑是靠一个 currentInterceptorIndex 下标控制,并以此下标为依据顺序执行增强器的通知逻辑。

你可能感兴趣的:(Spring,spring,aop)