全栈开发知识库

AOP

AOP(Aspect Oriented Programing),面向切面编程。 是OOP(Object Oriented Programing)面向对象编程的延续。

在OOP思想中,我们会把问题划分为各个模块,如语言、表情等。
在划分这些模块的过程中,也会出现一些共同特征(如埋点)。它的逻辑被分散到了各个模块,导致了代码复杂度提高,可复用性降低。

而AOP,就是将各个模块中的通用逻辑抽离出来。 我们将这些逻辑视为Aspect(切面),然后动态地把代码插入到类的指定方法、指定位置中。

一句话概括: 在运行时,动态的将代码切入到类的指定方法、指定位置上的编程思想就是面相切面的编程。

一般而言,我们管切入到指定类指定方法的代码片段称为切面,而切入到哪些类、哪些方法则叫切入点。有了AOP,我们就可以把几个类共有的代码,抽取到一个切片中,等到需要时再切入对象中去,从而改变其原有的行为。

AOP的实现方式

静态AOP

在编译器,切面直接以字节码的形式编译到目标字节码文件中。

  1. AspectJ
    AspectJ属于静态AOP,它是在编译时进行增强,会在编译时期将AOP逻辑织入到代码中。

由于是在编译器织入,所以它的优点是不影响运行时性能,缺点是不够灵活。

  1. AbstractProcessor
    自定义一个AbstractProcessor,在编译期去解析编译的类,并且根据需求生成一个实现了特定接口的子类(代理类)

动态AOP

  1. JDK动态代理
    通过实现InvocationHandler接口,可以实现对一个类的动态代理,通过动态代理可以生成代理类,从而在代理类方法中,在执行被代理类方法前后,添加自己的实现内容,从而实现AOP。

  2. 动态字节码生成
    在运行期,目标类加载后,动态构建字节码文件生成目标类的子类,将切面逻辑加入到子类中,没有接口也可以织入,但扩展类的实例方法为final时,则无法进行织入。比如Cglib

CGLIB是一个功能强大,高性能的代码生成包。它为没有实现接口的类提供代理,为JDK的动态代理提供了很好的补充。通常可以使用Java的动态代理创建代理,但当要代理的类没有实现接口或者为了更好的性能,CGLIB是一个好的选择。

  1. 自定义类加载器
    在运行期,目标加载前,将切面逻辑加到目标字节码里。如:Javassist

Javassist是可以动态编辑Java字节码的类库。它可以在Java程序运行时定义一个新的类,并加载到JVM中;还可以在JVM加载时修改一个类文件。

  1. 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)会:

  1. 扫描所有.class,找匹配切点的位置
  2. 按通知类型,在该位置直接修改字节码插入代码
  3. 输出新的.class

这意味着: AOP的开销在编译期,运行期几乎无额外开销(仅多几行字节码)。

本页由 Markdown 自动渲染 · 共 628 篇笔记