这是我参与11月更文挑战的第7天,活动详情查看:2021最后一次更文挑战
如何概述AOP
以下答案仅供参考,可根据个人理解和知识储备进行实际调整:
AOP 面向切面编程,全称 Aspect Oriented Programming ,它是 OOP 的补充。OOP 关注的核心是对象,AOP 的核心是切面(Aspect)。AOP 可以在不修改功能代码本身的前提下,使用运行时动态代理的技术对已有代码逻辑增强。AOP 可以实现组件化、可插拔式的功能扩展,通过简单配置即可将功能增强到指定的切入点。
学习一些概念:
目标对象.代理对象,连接点,切入点,通知.织入
这里举个去医院看病做手术的例子:有病人,有病人引导员(类似挂号),有科室
Target:目标对象
这个应该是最好理解的了,目标对象就是被代理的对象 .这里就相当于需要看病的病人
Proxy:代理对象
也是很好理解吧,代理对象就是上面代码中 Proxy.newProxyInstance 返回的结果。
在上面的场景中,*中间的引导员 + 左边的病人,组合形成一个代理对象(代理对象中还包含原始对象本身)。
JoinPoint:连接点
所谓连接点,可以简单的理解为目标对象的所属类中,定义的所有方法
上面场景中就相当于病人此次身上需要检查的部位,这就是连接点的概念
Pointcut:切入点
切入点,它的含义是那些被拦截 / 被增强的连接点,切入点可以是 0 个或多个(甚至全部)连接点的组合。
注意,切入点一定是连接点,连接点不一定是切入点
切入点类似手术的概念,所以需要做手术的一定是某个部位,病人的部位不一定都是要做手术的
Advice:通知
Advice 直接翻译过来叫通知,但这个概念似乎很抽象,所以我打算换一个词:增强的逻辑,也就是增强的代码。
通知的含义相当于此次手术对病人身上某个部位的具体处理:是对某个部位进行切除是新增还是修缮....
Weaving:织入
从名字上听起来,它有点像一个动作,而且我们先瞎猜一手,也能猜个八九不离十:织入就是将 Advice 通知应用到 Target 目标对象,进而生成 Proxy 代理对象的过程。
Proxy 代理对象 = Target 目标对象 + Advice 通知,这个算式中的加号,就是织入
这个场景中相当于病人需要做手术签署一些承诺书等协议
通知的类型
-
Before 前置通知:目标对象的方法调用之前触发
-
After 后置通知:目标对象的方法调用之后触发
-
AfterReturning 返回通知:目标对象的方法调用完成,在返回结果值之后触发
-
AfterThrowing 异常通知:目标对象的方法运行中抛出 / 触发异常后触发
-
注意一点,AfterReturning 与 AfterThrowing 两者是互斥的!如果方法调用成功无异常,则会有返回值;如果方法抛出了异常,则不会有返回值。
-
Around 环绕通知:编程式控制目标对象的方法调用。环绕通知是所有通知类型中可操作范围最大的一种,因为它可以直接拿到目标对象,以及要执行的方法,所以环绕通知可以任意的在目标对象的方法调用前后搞事,甚至不调用目标对象的方
jdk动态代理和Cglib动态代理的对比
- jdk 动态代理要求被代理的对象所属类至少实现一个接口,它是 jdk 内置的机制
- Cglib 动态代理无此限制,使用字节码增强技术实现,需要依赖第三方 Cglib 包
- jdk 动态代理的代理对象创建速度快,执行速度慢;Cglib 动态代理的代理对象创建速度慢,执行速度快
AOP的失效场景
-
代理对象调用自身的方法时,AOP 通知会失效
- 即在代理对象中直接调用
this.xxx方法 - 正确做法是借助
AopContext取到当前代理对象并强转,之后调用,这样 AOP 通知依然会执行
- 即在代理对象中直接调用
-
代理对象在后置处理器还没有初始化的时候,提前创建了,则 AOP 通知不会织入
- 由于 AOP 是借助
BeanPostProcessor实现,如果BeanPostProcessor还没有初始化好,目标对象已经创建了,则不可能再生成代理对象
- 由于 AOP 是借助
-
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
- 正确做法是避免两个包扫描的部分产生交集
todo :需补充源码解读