Android AOP编程之AspectJ
Posted 殇神马
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android AOP编程之AspectJ相关的知识,希望对你有一定的参考价值。
一、AspectJ
Aspectj是一个AOP框架,也是通过对字节码操作,来实现AOP的,但是与ASM需要开发者直接编写操作字节码的代码之外,使用Aspectj不需要开发者直接编写操作字节码代码, 而是只需要按照规范编写我们需要处理的Java代码即可;由Aspectj帮助我们将编写的Java代码的字节码织入到.class字节码文件中实现字节码插桩操作;
二、AspectJ中的基本概念
1.切面 Aspect
我们在学习AOP的时候,知道AOP实际就是针对的是业务逻辑中的某个处理步骤,比如日志处理,登录判断,权限获取,数据埋点,异常处理,这些很多业务逻辑中都需要的,可以从业务逻辑中单独抽出来的,这个在需要业务逻辑处都需要,可以从业务逻辑抽离出来的部分,我们称之为一个切面。
我们到时候需要定义一个切面类,使用@Aspect注解,表示是切面处理类;
@Aspect
public class TagAspect {
....
}
2.连接点 JoinPoint
就是我们切面可以插入的地方,也就是我们字节码可以织入的地方,比如方法的调用处,方法的执行处,都是连接点,也即我们可以织入字节码的地方;
JoinPoint | 说明 |
---|---|
method call | 方法调用处 |
method execution | 方法执行处 |
constructor call | 构造方法调用处 |
constructor execution | 构造方法执行处 |
field get | field get |
field set | 设置成员处 |
static initialization | static块初始化 |
handler | 异常处理处 |
这些都是JointPoint
3.切点 PointCut
切点(PointCut)很简单,我们上面说了连接点,切点就是具体是哪一个连接点,比具体如是在哪一个或哪一种方法中进行代码的织入;
那如何筛选出具体是哪一个连接点,我们在切面类中定义方法,使用@Pointcut注解,表示定义的是切点,注解值是一个表达式: 连接点类型(签名),表示具体是哪一个或者哪一类连接点;
比如连接点是一个方法执行处,表达式规范如下:
execution(@注解? <修饰符模式>? <返回类型模式> <方法名模式>(<参数模式>) <异常模式>?)
execution表示的是方法的执行类连接点;括号里面就是方法的签名;
@注解 :表示的是方法使用的注解类型(要写全类名:@com.example.aspectjmodule.Tag)
?表示可选的,不是必须要写的
<修饰符模式>: 就是方法的修饰符 public,private,protected等,也不是必填的
*表示任意类型
<返回类型模式> :方法返回值类型,void,int,等 , *表示任意类型 必填
方法名: 表示方法名字
test 就是表示方法名字是testDev所有方法
com.example…* 表示是com.example包下的任意方法
*表示任意方法
等等写法
<参数模式> :方法的形参类型
(int) 一个int类型形参
(int,int) 两个int类型形参
(. .) 任意个数,任意类型形参
@Aspect
public class TagAspect {
@Pointcut("execution(@execution(@com.example.aspectjmodule.Tag * *(..)))")
public void tagPointCut(){
}
}
JoinPoint | 说明 | PointCut |
---|---|---|
method call | 方法调用处 | call(MethodSignature) |
method execution | 方法执行处 | execution(MethodSignature) |
constructor call | 构造方法调用处 | call(ConstructorSignature) |
constructor execution | 构造方法执行处 | execution(ConstructorSignature) |
field get | field get | get(FieldSignature) |
field set | 设置成员处 | set(FieldSignature) |
static initialization | static块初始化 | staticinitialization(TypeSignature) |
handler | 异常处理处 | handler(TypeSignature) |
三、使用Aspectj实现字节码插桩
1.Aspectj的集成步骤
(1)在android项目下创建一个Module (Android Library), 一般会将Aspectj相关的代码放到一个单独的Module;所以先创建一个Module,名称为AspectjModule
(2) 在Aspectj Module的build.gradle下的buildsc添加Aspectj插件的依赖
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'org.aspectj:aspectjtools:1.9.7'
}
}
注意这个buildscript块必须要放到build.gradle的plugins{}块的上面,
否则会报如下错误:
all buildscript {} blocks must appear before any plugins {} blocks in the script
(3)在Module的build.gradle的dependencies下添加Aaspectj的依赖
dependencies {
api 'org.aspectj:aspectjrt:1.9.7'
}
(4)在Aspectj Module的build.gradle下添加如下构建脚本
import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main
android.libraryVariants.all { variant ->
JavaCompile javaCompile = variant.javaCompile
javaCompile.doLast {
String[] args = ["-showWeaveInfo",
"-1.5",
"-inpath", javaCompile.destinationDir.toString(),
"-aspectpath", javaCompile.classpath.asPath,
"-d", javaCompile.destinationDir.toString(),
"-classpath", javaCompile.classpath.asPath,
"-bootclasspath", project.android.bootClasspath.join(File.pathSeparator)
]
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:
log.error message.message, message.thrown
break;
case IMessage.WARNING:
case IMessage.INFO:
log.info message.message, message.thrown
break;
case IMessage.DEBUG:
log.debug message.message, message.thrown
break;
}
}
}
}
(5) 将Aspectj Module 添加为app 主Module的的依赖Moduleapp Module的
app 的build.gradle中dependencies添加如下
dependencies {
implementation project(path: ':AspectjModule')
}
(6)在app主Module中也要添加Aspectj的插件依赖,以及上面的构建脚本,但是app主Module的构建脚本稍有不同:
app主module 也即Application Module
用的是project.android.applicationVariants.all
AspectjModule也即Android Libdary Module使用的是android.libraryVariants.all
app主Module构建脚本如下
import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main
project.android.applicationVariants.all { variant ->
JavaCompile javaCompile = variant.javaCompile
javaCompile.doLast {
String[] args = ["-showWeaveInfo",
"-1.5",
"-inpath", javaCompile.destinationDir.toString(),
"-aspectpath", javaCompile.classpath.asPath,
"-d", javaCompile.destinationDir.toString(),
"-classpath", javaCompile.classpath.asPath,
"-bootclasspath", project.android.bootClasspath.join(File.pathSeparator)
]
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:
log.error message.message, message.thrown
break;
case IMessage.WARNING:
case IMessage.INFO:
log.info message.message, message.thrown
break;
case IMessage.DEBUG:
log.debug message.message, message.thrown
break;
}
}
}
}
这里要注意的是,不仅仅是app主Module,其他只要要使用Aspectj的Module,也是要添加Aspectj的插件依赖,以及构建脚本的;
上面就是集成Aspectj的步骤
2.用Aspectj实现字节码插桩
集成了Aspectj之后,下面我们来使用Aspectj实现一个功能;
比如我们希望在某些方法开始执行,和结束执行添加打印日志;
Aspectj的相关代码,我们就在AspectjModule中去编写;
(1)首先我们定义一个注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Tag {
String value();
}
(2)定义一个Aspectj处理类,Aspectj相关处理代码会在这个类中
@Aspect
public class TagAspect {
}
Aspectj处理类要是用@Aspect注解
(3)定义PointCut(切点)
定义切点:
定义一个方法,名字任意,使用@PointCut注解,@PointCut注解值是筛选具体的连接点的表达式
@Pointcut("execution(@com.example.aspectjmodule.Tag * *(..))")
public void tagPointCut() {
}
这个切点的定义表示是所有使用了com.example.aspectjmodule.Tag这个注解的方法
(4)定义代码织入的地方,并编写织入代码
也是要定义一个方法,方法名任意,然后我们这里给方法使用了@Around注解
注解的值就是切点的方法名加形参 tagPointCut()
@Before 前置通知 切点方法执行之前通知
@After 后置通知 切点方法执行之后通知
@Around 环绕通知 可以实现前置后置通知
@AfterReturning 返回通知 在切点方法返回之后执行
@AfterThrowing 异常通知 切点抛出异常时执行
@Around("tagPointCut()")
public Object around(ProceedingJoinPoint joinPoint) {
//拿到方法的签名
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
//类名
String className = methodSignature.getDeclaringType().getSimpleName();
//方法名
String methodName = methodSignature.getName();
Tag tagAnnotation = methodSignature.getMethod().getAnnotation(Tag.class);
String value = tagAnnotation.value();
Log.e(value, "begin execute"); //方法执行前添加打印代码
Object proceed = null;
try {
proceed = joinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
Log.e(value, "end execute"); //方法执行后添加打印代码
return proceed;
}
我们在方法执行的前后都添加了打印日志
(5)我们需要添加打印的方法使用Tag注解
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
test();
}
@Tag("TAG")
public void test() {
Log.e(TAG, "test: ");
}
}
然后运行程序,运行程序之后,logcat中日志
begin execute
test:
end execute
在test方法执行前后成功添加了打印日志
以上就是使用AspectJ实现字节码插桩的基本流程;
注意:我们在使用Java编写的Android项目中可以这样使用AspectJ,但是如果是Kotlin编写的Android项目,这样使用AspectJ是无效的,但是我们使用gradle_plugin_android_aspectjx这个第三方库,一个基于AspectJ并在此基础上扩展出来可应用于Android开发平台的AOP框架,可作用于java源码,class文件及jar包,同时支持kotlin的应用;
欢迎扫码订阅公众号,公众号主要分享原创或转载移动开发技术文章,和广大移动开发者一起学习成长!
以上是关于Android AOP编程之AspectJ的主要内容,如果未能解决你的问题,请参考以下文章