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
方法中将目标对象包装为代理对象。这里面涉及到几个核心步骤:
- 检查当前初始化的 bean 是否可以被 AOP 代理(检查是否有匹配的增强器)
- 如果存在,则根据当前初始化的 bean 所属类有无实现接口,以及 AOP 的全局配置,决定使用哪种代理方案
- 将目标对象包装为
TargetSource
,并以此为原型生成代理对象
3.2 代理对象的通知逻辑是如何执行的
代理对象被构造后,执行方法会进入 JdkDynamicAopProxy
/ CglibAopProxy
中,并构造 ReflectiveMethodInvocation
并依次执行这些织入的通知。执行通知的逻辑是靠一个 currentInterceptorIndex
下标控制,并以此下标为依据顺序执行增强器的通知逻辑。
没有回复内容