
一、AspectJ 简介
Hi,大家好,这里是承香墨影!
AspectJ 是 Java 语言 AOP(面向切面编程)的一种实现方案。
AspectJ 有两种实现方式:
- 使用 Java 语言和注解,然后通过 AspectJ 提供的编织器,编织代码到目标 class 文件;
- 直接使用 AspectJ 语言编写,然后使用 ajc 编译器用来编译 aj 文件,生成 java 标准的 class 文件。
AspectJ 语言的语法和 Java 一样,只是比 Java 语言多了一些关键字,但由于 Android Studio 并没有提供 AspectJ 语法的支持,所以在 Android 开发中使用 AspectJ 只能使用注解的方式来实现。
二、一些基本概念
下面通过注解的实现方式来解释一些基本的概念。
2.1 Aspect(切面)
一个切面是一个独立的功能实现,一个程序可以定义多个切面,定义切面需要新建一个类并加上 @Aspect 注解。
例如:SampleAspect 就是一个切面 ,这个切面实现了打印所有 Activity 中 onCreate() 方法耗时的功能。
@Aspect
public class SampleAspect {
@Pointcut("execution(void android.support.v4.app.FragmentActivity+.onCreate(..))")
public void activityOnCreate() {
}
@Around("activityOnCreate()")
public Object activityOnCreateTime(ProceedingJoinPoint joinPoint) {
Object object = null;
long startTime = System.currentTimeMillis();
try {
object = joinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
Log.d("chao","activityOnCreateTime:"+(System.currentTimeMillis() - startTime));
return object;
}
}2.2 JointPoint(链接点)
链接点代表了程序中可以切入代码的位置,包括函数的调用和执行,类的初始化,异常处理等,链接点就是利用 AspectJ 可以侵入修改的地方。
例如 Activity 中 onCreate() 方法的调用和执行,Activity 的初始化,构造方法的执行等,可以在这些 JointPoint(链接点)切入自己想要的代码。
2.3 PointCut(切点)
切点是具体的链接点,切点定义了需要织入代码的链接点,切点的定义有专门的语法。
例如:下面这个就代表一个切点,这个切点表示了所有 Activity 以及其子类的 onCreate() 方法的执行。
@Pointcut("execution(void android.support.v4.app.FragmentActivity+.onCreate(..))")
public void activityOnCreate() {
}2.4 Advice(通知)
通知代表对切点的监听,可以在这里编写需要织入的代码。通知包括:@Before 方法执行前,@After 方法执行后,@Around 方法执行前后。
例如:下面分别表示了切点 activityOnCreate 的执行前、执行后、执行前后的监听,其中 @Around 需要自己处理方法的执行,并且必须放在 @Before 和 @After 之前。
@Around("activityOnCreate()")
public Object activityOnCreateTime(ProceedingJoinPoint joinPoint) {
Object object = null;
long startTime = System.currentTimeMillis();
try {
object = joinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
Log.d("chao","activityOnCreateTime:"+(System.currentTimeMillis() - startTime));
return object;
}
@Before("activityOnCreate()")
public void onCreateBefore(JoinPoint joinPoint) {
Log.d("chao", "onCreateBefore" + joinPoint.getSignature().getDeclaringType() + ":" + joinPoint.getSignature().getDeclaringTypeName());
}
@After("activityOnCreate()")
public void onCreateAfter(JoinPoint joinPoint) {
Log.d("chao", "onCreateAfter" + joinPoint.getSignature().getDeclaringType() + ":" + joinPoint.getSignature().getDeclaringTypeName());
}三、匹配表达式
上面讲到了定义切点时需要用到专门的语法:匹配表达式。匹配表达式分为以下几种类型:
3.1 方法类型
1[!] [@Annotation] [public,protected,private] [static] [final] 返回值类型 [类名.]方法名(参数类型列表) [throws 异常类型]定义方法切点时需要用到方法类型的匹配表达式,其中 execution() 代表了方法的执行,[] 中的内容为可选。
如果是构造方法只需要把方法名替换为 new。例如:
@Pointcut("execution(@com.sohu.aspectj.AspectLog public static final boolean
com.sohu.aspectj.MainActivity.getIsAnnotation(java.lang.String,boolean)throws
java.lang.Exception)")
public void activityIsFirst() {
}
构造方法的切入点
@Pointcut("execution(public com.sohu.aspectj.KotlinClassFile.new(java.lang.String,boolean))")
public void kotlinClassInit() {
}3.2. 运算符以及特殊符号
- !非 && 与 || 或;
//所有Fragment类以及其子类testParams方法的调用
@Pointcut("execution(public * testParams(..)) &&
target(android.support.v4.app.Fragment+) && args(args)")
public void fragmentTestParams(boolean args,Fragment fragment) {
}- + 表示自身以及子类;
- * 表示任意类型;
- .. 表示任意长度类型;
//AspectLog注解的在包com.sohu.aspectj开头的所有包下的方法
@Pointcut("execution(@com.sohu.aspectj.AspectLog * com.sohu.aspectj..*(..))")
public void fragmentaspectLog(Fragment fragment) {
}3.3 handler 指定异常的处理
//com.sohu.aspectj包开头下的所有Exception的异常
@Pointcut("handler(java.lang.Exception)&&within(com.sohu.aspectj.*)")
public void fragmentException() {
}3.4 其它类型
- target 是指切点所在的类的对象,只能是对象,例如:不能是
target(android.support.v4.app.Fragment+)或者target(android.support.v4.app.*),只能是target(fragment); - with 是指指定类中的所有链接点,可以包括包名和类名,例如:
within(android.support.v4.app.Fragment+)或者within(android.support.v4.app.*);
@Pointcut("execution(public * testParams(..)) && target(fragment) && args(args)")
public void fragmentTestParams(boolean args,Fragment fragment) {
}
//Fragment及其子类中的所有链接点
@Pointcut("execution(public * testParams(..)) &&
within(android.support.v4.app.Fragment+) && args(args)")
public void fragmentWithTestParams(boolean args,Fragment fragment) {
}以上是一些常见的匹配表达式类型,更加高级的用法可以查看官方文档:匹配表达式官方文档。
四、自定义注解
AspectJ 可以自定义注解,来对需要注入代码的地方增加标志,下面通过一个打印方法信息的例子来说明一下自定义注解的使用方式:
4.1 编写注解类
使用 RUNTIME 是因为切面中要使用注解的值,如果切面中不使用注解的值则可以使用 CLASS 类型
@Target({ElementType.FIELD,ElementType.METHOD,ElementType.CONSTRUCTOR,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface AspectLog {
String[] checkString();
int checkCode();
}4.2 为需要切入的方法加上注解
@AspectLog(checkCode = 3,checkString = {"a","b","b"})
public void execute(String out) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}4.3 编写切面类处理注解并打印方法的一些信息
需要注意的是获取注解值,有两种方法:
- 加上
@annotation()标志后增加 AspectLog 参数; - 通过 ProceedingJoinPoint 得到注解值;
分别对应了 logAndExecute() 方法和 logAndExecute2() 方法。
@Aspect
public class SampleAspect {
@Pointcut("execution(@com.sohu.aspectj.AspectLog * *(..))&&@annotation(hyaspect)")
// @AspectLog 修饰的方法的执行
public void method(AspectLog hyaspect) {
}
// @AspectLog 修饰的方法的执行
@Pointcut("execution(@com.sohu.aspectj.AspectLog * *(..))")
public void method2() {
}
// @AspectLog 修饰的构造函数的执行
@Pointcut("execution(@com.sohu.aspectj.AspectLog *.new(..))&&@annotation(hyaspect)")
public void constructor(AspectLog hyaspect) {
}
// @AspectLog 修饰的构造函数的执行
@Pointcut("execution(@com.sohu.aspectj.AspectLog *.new(..))")
public void constructor2() {
}
@Around("method(hyaspect) || constructor(hyaspect)")
public Object logAndExecute(ProceedingJoinPoint joinPoint,AspectLog hyaspect) throws Throwable {
String[] checkString = hyaspect.checkString();//得到注解checkString的返回值
Log.d("chao", "-----AspectLog:needCheck:" + hyaspect.needCheck()+" checkCode:"+hyaspect.checkCode()+" checkString:"+checkString[0]);
//输出结果:-----AspectLog:needCheck:false checkCode:3 checkString:a
Log.d("chao", "-----className:" + joinPoint.getSignature().getDeclaringType());
//输出结果:-----className:class com.sohu.aspectj.MainActivity
Log.d("chao", "-----Methodname:" + joinPoint.getSignature().getName());
//输出结果: -----Methodname:execute
Log.d("chao", "-----ParamsType:" + ((CodeSignature)(joinPoint.getSignature())).getParameterTypes()[0]);
//输出结果:-----ParamsType:class java.lang.String
Log.d("chao", "-----ParamsValue:" + joinPoint.getArgs()[0]);
//输出结果:-----ParamsValue:myExecute
boolean hasReturnType = joinPoint.getSignature() instanceof MethodSignature
&& ((MethodSignature) joinPoint.getSignature()).getReturnType() != void.class;
Log.d("chao", "-----returnType:" + hasReturnType);
//输出结果:-----returnType:false
long startNanos = System.nanoTime();
Object result = joinPoint.proceed(); // 调用原来的方法
long stopNanos = System.nanoTime();
long lengthMillis = TimeUnit.NANOSECONDS.toMillis(stopNanos - startNanos);
Log.d("chao", "-----executeTime:" + lengthMillis);
//输出结果:-----executeTime:500
return result;
}
//另外一种写法
@Around("method2() || constructor2()")
public Object logAndExecute2(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature methodSignature = ((MethodSignature) joinPoint.getSignature());
AspectLog hyaspect = methodSignature.getMethod().getAnnotation(AspectLog.class);
String[] checkString = hyaspect.checkString();
Log.d("chao", "-----AspectLog2:needCheck:" + hyaspect.needCheck()+" checkCode:"+hyaspect.checkCode()+" checkString:"+checkString[0]);
Object result = joinPoint.proceed();
return result;
}
}五、在 Android 中的应用
在 AndroidStudio 中使用 AspectJ 需要分三步:
5.1 引入第三方注解包
在需要编写切面的 Module 中引入 'org.aspectj:aspectjrt:1.8.7' 里面包含了注解等代码。
dependencies {
//aspectj注解工具包
implementation 'org.aspectj:aspectjrt:1.8.7'
}5.2 引入 Gradle 插件
使用 AspectJ 的功能,必须通过插件来处理插入的代码。Gradle 插件的作用是处理切面类,植入代码。目前针对 Android 来说并没有官方 Gradle 插件,需要自己编写或者使用第三方成熟的插件。
下图所示是本地自定义的一个 Gradle 插件,使用这种本地 Gradle 插件的方式,则只需要在主 Module 的 build.gradle 中引入插件即可,具体实现细节下个章节会讲到。

5.3 编写切面类
编写切面类,编写切点和通知代码
六、Gradle 插件的实现
Gradle 插件实现如果不需要发布插件,则可以再编写本地插件。自定义插件的方式这里不再赘述,感兴趣的小伙伴可以参考官方文档。
6.1 新建插件 Module buildSrc
在其中 build.gradle 引用如下:
apply plugin: 'groovy'
repositories {
mavenLocal()
jcenter()
google()
}
dependencies {
implementation gradleApi()//gradle相关api
implementation localGroovy()//groovy支持api
implementation 'org.aspectj:aspectjtools:1.8.7'//aspectj工具
implementation 'com.android.tools.build:gradle:3.2.1'//gradle相关api
implementation 'org.aspectj:aspectjrt:1.8.7'//aspectj工具
}6.2 继承 Plugin<Project>
新建 HyAspectJPlugin 继承 Plugin<Project>,在 apply(project) 方法中执行一些操作:首先获取 java 编译器中的一些 java 文件,以及 class 文件目录,然后 java 编译器编译完成后,调用 aspectj 的编织工具,传入文件目录等参数,织入代码。织入的过程可以通过 MessageHandler 来输出 log 信息。
编织器需要的各个参数信息说明,如代码中的注释,更详细的参数信息参考官网。
//Plugin<Project>是gradle3.2.1中的api
class HyAspectJPlugin implements Plugin<Project> {
@Override
void apply(Project project) {
//AppExtension是gradle:3.2.1 中的api
project.extensions.getByType(AppExtension).getApplicationVariants().all { variants ->
JavaCompile javaCompiler = variants.javaCompiler
println("----------------inpath-------------" + javaCompiler.destinationDir.toString())
//输出内容为:/Users/allenzhang/AspectJ/app/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes
println("----------------aspectpath-------------" + javaCompiler.classpath.asPath)
//输出内容为:/Users/allenzhang/.gradle/caches/transforms-1/files-1.1/design-28.0.0.aar/cd2e80a662cae66082356ce68f010bb8/jars/classes.jar
//:/Users/allenzhang/.gradle/caches/transforms-1/files-1.1/appcompat-v7-28.0.0.aar/c54d7f8ccd6a59577eea394c9c81344c/jars/classes.jar
println("----------------bootclasspath-------------" + javaCompiler.options.bootstrapClasspath.asPath)
//输出内容为:/Users/allenzhang/Library/Android/sdk/platforms/android-28/android.jar
//:/Users/allenzhang/Library/Android/sdk/build-tools/28.0.3/core-lambda-stubs.jar
// -1.7 设置规范1.7,匹配java1.7
// -showWeaveInfo,输出编织过程信息
// -inpath class文件目录或者jar包, 源字节码,需要处理的类
// -aspectpath 定义的切面类
// -d 存放编辑产生的class文件
// -classpath ,所有class文件,源class,java包,编织时需要用到的一些处理类
javaCompile.doLast {
String[] args = [
"-showWeaveInfo",
"-1.7",
"-inpath", javaCompile.destinationDir.toString(),
"-aspectpath", javaCompiler.classpath.asPath,
"-d", javaCompile.destinationDir.toString(),
"-classpath", javaCompiler.classpath.asPath,
"-bootclasspath", javaCompile.options.bootstrapClasspath.asPath
]
MessageHandler handler = new MessageHandler(true)
new Main().run(args, handler)
def log = project.logger
for (IMessage message : handler.getMessages(null, true)) {
switch (message.getKind()) {
case IMessage.ABORT:
case IMessage.ERROR:
case IMessage.FAIL:
println("----------------ERROR-------------" + message.message)
log.error message.message, message.thrown
break
case IMessage.WARNING:
case :
println("----------------WARNING-------------" + message.message)
log.info message.message, message.thrown
break
case IMessage.DEBUG:
println("----------------DEBUG-------------" + message.message)
log.debug message.message, message.thrown
break
}
}
}
}
}
}6.3 新建配置文件
如下图所示:新建 resources/META-INF/gradle-plugins/、com.sohu.aspectj.properties,定义插件的名称为 com.sohu.aspectj。
文件中内容为:implementation-class=com.sohu.aspectj.HyAspectj。

七、一个优秀的 Gradle 插件
上面只是简单的插件编写方法,这种方法和 kotlin 冲突不能共存, 这是因为 kotlin 编译的 class 文件在另外的一个位置,还需要传入对应 class 文件夹的位置。下面介绍一款比较好的 gradle 插件。
相比上面简单的织入方式,有以下优点:
- 支持 InstantRun;
- 支持设置织入代码范围;
- 支持 kotlin 代码;
- 异步织入;
如下图所示:插件原理是通过注册一个 Transform,获取输入文件(这个输入文件包含了 kotlin 的 class 文件),过滤需要织入的文件,然后织入代码后输出到指定文件夹,感兴趣的小伙伴可以查看源码了解更详细的内容。

Transform 的介绍可以参考 Transform 的使用。
以上介绍了 AspectJ 的相关概念以及用法,还介绍了 Gradle 插件的编写方式。
AspectJ 是 Android 面向切面编程的一种常用的实现方式,在打印日志、统计埋点、统一业务逻辑处理、性能统计等方面有广泛的应用。
但匹配表达式的规则太多,容易混淆,以及没有官方 Gradle 插件的说明文档,这两点使 AspectJ 使用起来较为麻烦。在使用过程中只需要先掌握一些常见的用法,遇到不能满足需求的地方可以查阅官方文档寻找答案。
-- End --
















