AspectJ 使用及原理
一.简介
Aspect Oriented Programming(AOP)面向切面编程是目前比较流行的一种编程方式,切面是指从不同的角度来看待同一个事物,比如我们做一个Android app需求时候,是从业务逻辑角度考虑的,需要实现一个Activity,实现一个model,实现一个View,再在Activity里完成对View和model控制等等;再比如我们需要对整个app的所有Activity做一些性能监控,这时候就需要从整个项目的角度来考虑,需要实现的就是统一对每个Activity或基类做处理,而不是某个业务的Activity了。
AOP就是可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种编程思想,也就是不需要侵入代码(比如修改基类等)就可以实现功能模块,而AspectJ就是该思想的一种具体实现方式,它是一个面向切面的框架,它扩展了Java语言。
二.原理
AspectJ定义了AOP语法,所以它有一个专门的编译器用来生成遵守Java字节编码规范的Class文件。
AspectJ定义了一组注解,对应着一组概念,还有一套匹配表达式,我们通过书写匹配表达式,告知AspectJ我们想在哪些地方添加代码,然后再通过不同的注解,决定我们想以什么样的方式在源代码处做什么样的事,最后在编译时,AspectJ的编译器就会按照我们的想法,在源代码里注入新的代码,也可以理解为AspectJ hook住了编译过程,添加了一些代码。
先举个简单的小例子来验证下AspectJ的实现效果:
假如我们想要在所有的Activity的onCreate执行时,先输出一个log,那么我们定义自己的切面
@Aspect
class InheritAspect {
private companion object {
private const val TAG = "InheritAspect"
private const val ON_CREATE_EXECUTION = "execution(void *..*Activity.onCreate(..))"
}
@Pointcut(ON_CREATE_EXECUTION)
fun onCreateExecution() {
}
@Before("onCreateExecution()")
fun beforeOnCreateExecution(joinPoint: JoinPoint) {
Log.i(TAG,"onCreate start")
}
}
代码先不用管,意思就是定义了一个切面,拦截的是所有Activity的onCreate方法的执行,并且在执行时,输出一个log
下面我们来看看,运行后其中一个Activity的文件,反编译过后的结果:
//源代码
protected void onCreate(@Nullable Bundle var1) {
super.onCreate(var1);
}
//运行后
protected void onCreate(@Nullable Bundle var1) {
JoinPoint var2 = Factory.makeJP(ajc$tjp_7, this, this, var1);
//拿到InheritAspect的单例对象
InheritAspect.aspectOf().beforeOnCreateExecution(var2);
super.onCreate(var1);
}
我们可以看到,在这个Activity的onCreate方法中,先执行的就是InheritAspect类的beforeOnCreateExecution方法(JoinPoint对象就是包装了一些切点的信息),该方法就是我们上面定义的那个方法,输出了log,可见,AspectJ的编译器按照我们的想法,在源码上添加了代码。
知道了大概原理,我们就来看看怎么使用吧!
三.使用
(一)基本概念
1.连接点(JoinPoint)
连接点是程序中可以插入代码的地方,比如调用一个方法、一个方法执行中、调用一个构造器、一个构造器执行中等等,被AspectJ支持的连接点如下:
2.切点(PointCut)
切点其实就是想要在哪些地方插入一段代码,也就是一组连接点集合的逻辑组合关系,比如一个切点可以定义为:调用A类的a方法时||调用B类的b方法时;这样当调用A类的a方法这个连接点或者调用B类的b方法这个连接点时都会被该切点切入,从而插入代码
3.插入逻辑(Advice)
上面说的是定义的切点,即静态点,有了静态点后,就需要插入代码了,插入代码的方式AspectJ也定义了几种:
-
Before
在连接点之前插入代码
-
After
在连接点之后插入代码
-
Around
代理连接点,可以自定义是否执行连接点或返回何种结果等
4.切面(Aspect)
切面就是包装了连接点、切点、插入逻辑的单一模块,也是AspectJ扫描的单元,一个切面定义了一组AOP功能
以上是AspectJ的基本概念,下面带着这些概念,进入到具体的使用学习中吧!
(二)类型匹配表达式
对于AOP来说,最重要的就是要准确的找到想要切入的点了,AspectJ提供了一套匹配表达式来完成,主要的结构如下({}为可选项):
{注解}{修饰符}<返回值>{类}<方法名><方法参数>
下面我们来结合例子来学习,如何使用匹配表达式定位到具体方法
1.注解
此处的注解为标注在方法上的注解
@java.lang.Deprecated * *(..)
表示标有@java.lang.Deprecated注解的所有方法
2.修饰符
public * *(..)
表示所有public的方法,这个不用多说
3.返回值
* *(..)
*表示任意类型,所以该表达式表示任何方法
void *(..)
表示返回值为void的所有方法
java.lang.String *(..)
表示返会String类型的所有方法
java.lang.String+ *(..)
+表示子类,所以该表达式表示返回String及其子类类型的所有方法
java.lang.* *(..)
表示返回java.lang包下所有类型的方法
java.lang.String* *(..)
*也可以作为任意字符个数的匹配(前缀后缀),该表达式表示返回java.lang包下,以String为前缀的所有类型,的所有方法
java..* *(..)
…表示的是当前包及其任意子包下(包括子包的子包等),该表达式表示返回java包及其底下所有包下的类型,的所有方法
(@java.lang.Deprecated *) *(..)
此处注解声明在*上(括号包住),表示该返回值的类型上有@java.lang.Deprecated注解的所有方法
4.类
类的匹配规则大多与返回值类似
* com..*Activity+.*(..)
表示com包及其子包下,以Activity为后缀的所有类及其子类,的所有方法,此处+、*与…和返回值处一样
* (@java.lang.Deprecated *).*(..)
表示所有标有@java.lang.Deprecated注解的类的所有方法
5.方法名
方法名就相对简单了