AOP
AOP(Aspect Oriented Programing),面向切面编程。 是OOP(Object Oriented Programing)面向对象编程的延续。
在OOP思想中,我们会把问题划分为各个模块,如语言、表情等。
在划分这些模块的过程中,也会出现一些共同特征(如埋点)。它的逻辑被分散到了各个模块,导致了代码复杂度提高,可复用性降低。
而AOP,就是将各个模块中的通用逻辑抽离出来。 我们将这些逻辑视为Aspect(切面),然后动态地把代码插入到类的指定方法、指定位置中。
一句话概括: 在运行时,动态的将代码切入到类的指定方法、指定位置上的编程思想就是面相切面的编程。
一般而言,我们管切入到指定类指定方法的代码片段称为切面,而切入到哪些类、哪些方法则叫切入点。有了AOP,我们就可以把几个类共有的代码,抽取到一个切片中,等到需要时再切入对象中去,从而改变其原有的行为。
AOP的实现方式
静态AOP
在编译器,切面直接以字节码的形式编译到目标字节码文件中。
- AspectJ
AspectJ属于静态AOP,它是在编译时进行增强,会在编译时期将AOP逻辑织入到代码中。
由于是在编译器织入,所以它的优点是不影响运行时性能,缺点是不够灵活。
- AbstractProcessor
自定义一个AbstractProcessor,在编译期去解析编译的类,并且根据需求生成一个实现了特定接口的子类(代理类)
动态AOP
-
JDK动态代理
通过实现InvocationHandler接口,可以实现对一个类的动态代理,通过动态代理可以生成代理类,从而在代理类方法中,在执行被代理类方法前后,添加自己的实现内容,从而实现AOP。 -
动态字节码生成
在运行期,目标类加载后,动态构建字节码文件生成目标类的子类,将切面逻辑加入到子类中,没有接口也可以织入,但扩展类的实例方法为final时,则无法进行织入。比如Cglib
CGLIB是一个功能强大,高性能的代码生成包。它为没有实现接口的类提供代理,为JDK的动态代理提供了很好的补充。通常可以使用Java的动态代理创建代理,但当要代理的类没有实现接口或者为了更好的性能,CGLIB是一个好的选择。
- 自定义类加载器
在运行期,目标加载前,将切面逻辑加到目标字节码里。如:Javassist
Javassist是可以动态编辑Java字节码的类库。它可以在Java程序运行时定义一个新的类,并加载到JVM中;还可以在JVM加载时修改一个类文件。
- ASM
ASM可以在编译期直接修改编译出的字节码文件,也可以像Javassit一样,在运行期,类文件加载前,去修改字节码。
AOP核心概念
1.JoinPoint(连接点)
程序执行过程中可以被“插入”的点。例如:
- 方法被调用时
- 方法执行时
- 构造函数被调用时
- 字段被读取/赋值时
- 异常抛出时
2.PointCut(切点)
对JoinPoint的筛选规则,告诉AOP“我要在哪些点下手”。
例如: 所有Activity的onCreate方法就是一个切点。
3. Advice(通知/增强)
在切点处要执行的代码。分5种:
- @Before: 切点执行前
- @After: 切点执行后(无论成功失败)
- @AfterReturning: 切点正常返回后
- @AfterThrowing: 切点抛出异常后
- @Around: 包裹切点(最强大,可控制是否执行原方法)
4. Aspect(切面)
切点 + 通知 = 切面
一个Java类用@Aspect标记后就是切面。
5. Weaving(织入)
把切面代码插入到目标代码中的过程。 织入时机有三种:
- 编译期织入(AspectJ主流方式)
- 类加载期织入
- 运行期织入(JDK/CGLIB动态代理,Spring用的多)
6. Target(目标对象)
被切面影响的对象
AOP在Android的实现原理
Java/Spring的AOP vs Android的AOP
Spring主要用运行时动态代理(JDK Porxy/ CGLIB),但Android由于:
- ART/Dalvik虚拟机限制
- 性能要求高,运行时反射代价大
- 类加载机制不同
所以Android几乎都用AspectJ编译期自己码织入。
编译期织入原理
┌─────────────┐ javac ┌─────────────┐ ajc(AspectJ) ┌──────────────┐
│ .java │ ──────────> │ .class │ ──────────────> │ 织入后.class │
└─────────────┘ └─────────────┘ └──────────────┘
↑
┌─────────────┐
│ Aspect.java │
└─────────────┘
ajc(AspcetJ Compiler)会:
- 扫描所有.class,找匹配切点的位置
- 按通知类型,在该位置直接修改字节码插入代码
- 输出新的.class
这意味着: AOP的开销在编译期,运行期几乎无额外开销(仅多几行字节码)。