AOP是OOP的延续,是Aspect Oriented Programming的缩写,意思是面向切面编程。可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术。AOP实际是GoF设计模式的延续,设计模式孜孜不倦追求的是调用者和被调用者之间的解耦,AOP可以说也是这种目标的一种实现。
AspectJ是AOP编程思想的一个实践,它是一种几乎和Java完全一样的语言,而且完全兼容Java(AspectJ应该就是一种扩展Java,但它不是像Groovy那样的拓展)。
一、概念
先来了解一下AOP的相关概念:
- Aspect 切面:官方的抽象定义为“一个关注点的模块化,这个关注点可能会横切多个对象”。
- Joinpoint 连接点:程序执行过程中的某一行为,例如,UserService.get的调用或者UserService.delete抛出异常等行为。
- Advice 通知:“切面”对于某个“连接点”所产生的动作,一个“切面”可以包含多个“Advice”。
- Pointcut 切入点 :匹配连接点的断言,在AOP中通知和一个切入点表达式关联。
- Weaving 编织:主要是在编译期使用AJC将切面的代码注入到目标中, 并生成出代码混合过的.class的过程。
通知(Advice)类型:
- Before 前置通知:在某连接点(JoinPoint)之前执行的通知,但这个通知不能阻止连接点前的执行。
- After 后置通知:当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。
- AfterReturning 返回后通知:在某连接点正常完成后执行的通知,不包括抛出异常的情况。
- Around 环绕通知:包围一个连接点的通知。可以在方法的调用前后完成自定义的行为,也可以选择不执行。
- AfterThrowing 抛出异常后通知:在方法抛出异常退出时执行的通知。
注:可以将多个通知应用到一个目标对象上,即可以将多个切面织入到同一目标对象。
二、配置aspectJ
1、添加aspectjx的classpath
在project的build.gradle中添加如下代码
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.2.0'
classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.4'
}
}
其中com.android.tools.build:gradle的版本需要小于3.3.0,不然会报错,可能aspectjx目前还不支持3.3以上的gradle版本。
2、添加依赖
如下,所示
dependencies {
implementation 'org.aspectj:aspectjrt:1.8.14'
}
该依赖可以添加在library Module的build.gradle中,也可以在app的build.gradle中。
3、应用插件
在app module的中build.gradle中添加如下代码。
apply plugin: 'android-aspectjx'
到此,配置完毕。
三、应用
1、新建自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface BehaviorAnno {
String value();
}
该注解目标对象是方法,是运行时注解
2、定义切面
// 新建切面
@Aspect
public class BehaviorAspect {
// 切入点
@Pointcut("execution(@per.wsj.aspectjdemo.anno.BehaviorAnno * * (..))")
public void clickBehavior() {
}
// // 配置前置通知
// @Before("clickBehavior()")
// public void beforeMethod(JoinPoint joinPoint) throws Throwable {
// Log.e("MainActivity", "点击前-----------");
// }
// // 配置后置通知
// @After("clickBehavior()")
// public void afterMethod(JoinPoint joinPoint) throws Throwable {
// Log.e("MainActivity", "点击后------------");
// }
@Around("clickBehavior()")
public void aroundMethod(ProceedingJoinPoint joinPoint) throws Throwable {
Log.e("MainActivity", "点击前");
joinPoint.proceed();
Log.e("MainActivity", "点击后");
}
}
注意:
(1)同一个切入点,After、Before、Around最多可以有两个,三者不能同时出现。
(2)不同切入点@After和@Before可以共存,但@Around不能与@After/@Before共存,否则无法编译。
3、调用
在需要做跟踪的方法上添加@BehaviorAnno注解,如下:
@BehaviorAnno("点击fab按钮")
public void doSth(){
Log.e("MainActivity","点击了");
}
在切面中添加了针对BehaviorAnno 的Around类型的通知,所以可以在该方法执行前和执行后做自定义操作。执行结果如下:
07-10 16:45:42.751 24153-24153/? E/MainActivity: 点击前
07-10 16:45:42.753 24153-24153/? E/MainActivity: 点击了
07-10 16:45:42.753 24153-24153/? E/MainActivity: 点击后
4、获取注解的参数
创建注解时,添加了个String value();参数,调用时传入了参数,那么如何在切面中获取该参数呢?
代码如下:
@Around("clickBehavior()")
public void aroundMethod(ProceedingJoinPoint joinPoint) throws Throwable {
Log.e("MainActivity", "点击前");
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
BehaviorAnno anno = method.getAnnotation(BehaviorAnno.class);
if(anno!=null){
Log.e("MainActivity", "注解参数:"+ anno.value());
}
joinPoint.proceed();
Log.e("MainActivity", "点击后");
}
通过ProceedingJoinPoint 可以获取MethodSignature ,MethodSignature 可以获取点击时对应的方法,也就是doSth()方法,根据Method可以获取对应的注解。
执行结果如下:
07-10 17:10:10.595 30224-30224/? E/MainActivity: 点击前
07-10 17:10:10.596 30224-30224/? E/MainActivity: 注解参数:点击fab按钮
07-10 17:10:10.598 30224-30224/? E/MainActivity: 点击了
07-10 17:10:10.598 30224-30224/? E/MainActivity: 点击后
5、获取方法的参数
(1)获取方法参数名:
((CodeSignature) joinPoint.getSignature()).getParameterNames();
(2)获取方法的传入参数:
joinPoint.getArgs();
具体代码如下:
@Around("clickBehavior()")
public void aroundMethod(ProceedingJoinPoint joinPoint) throws Throwable {
Log.e("MainActivity", "点击前");
String[] paramNames = ((CodeSignature) joinPoint.getSignature()).getParameterNames();
Object[] params = joinPoint.getArgs();
for (int i = 0; i < paramNames.length; i++) {
Log.e("MainActivity", "方法名:" + paramNames[i] + " 方法参数:" + params[i]);
}
joinPoint.proceed();
Log.e("MainActivity", "点击后");
}
运行结果如下:
07-10 17:27:23.462 4350-4350/? E/MainActivity: 点击前
07-10 17:27:23.462 4350-4350/? E/MainActivity: 方法名:name 方法参数:张三
07-10 17:27:23.463 4350-4350/? E/MainActivity: 点击了
07-10 17:27:23.463 4350-4350/? E/MainActivity: 点击后
6、获取context
在切面中难免遇到用context的情况,可以通过JoinPoint.getThis()方法获取。如下:
@Before("beforeBehavior()")
public void beforeMethod(JoinPoint joinPoint) throws Throwable {
Log.e("MainActivity", "点击前-----------");
Context context = (Context) joinPoint.getThis();
Toast.makeText(context, "执行前", Toast.LENGTH_SHORT).show();
}
四、@Pointcut语法
@pointcut规则可以对任何情况进行匹配,具体语法如下:
分类pointcuts 遵循特定的语法用于捕获每一个种类的可使用连接点。 主要的种类:
- 方法执行:execution(MethodSignature)
- 方法调用:call(MethodSignature)
- 构造器执行:execution(ConstructorSignature)
- 构造器调用:call(ConstructorSignature)
- 类初始化:staticinitialization(TypeSignature)
- 属性读操作:get(FieldSignature)
- 属性写操作:set(FieldSignature)
- 例外处理执行:handler(TypeSignature)
- 对象初始化:initialization(ConstructorSignature)
- 对象预先初始化:preinitialization(ConstructorSignature)
AspectJ类型匹配的通配符:
*:匹配任何数量字符;
..:匹配任何数量字符的重复,如在类型模式中匹配任何数量子包;而在方法参数模式中匹配任何数量参数。
+:匹配指定类型的子类型;仅能作为后缀放在类型模式后边。
java.lang.String 匹配String类型;
java.*.String 匹配java包下的任何“一级子包”下的String类型;
如匹配java.lang.String,但不匹配java.lang.ss.String
java..* 匹配java包及任何子包下的任何类型;
如匹配java.lang.String、java.lang.annotation.Annotation
java.lang.*ing 匹配任何java.lang包下的以ing结尾的类型;
java.lang.Number+ 匹配java.lang包下的任何Number的自类型;
如匹配java.lang.Integer,也匹配java.math.BigInteger
参考:
https://www.cnblogs.com/davidwang456/p/4013631.html
https://github.com/HujiangTechnology/gradle_plugin_android_aspectjx
https://www.mekau.com/4880.html