AspectJ
什么是AspectJ?
AspectJ实际上是对AOP编程思想的一个实践,它是一种几乎和Java完全一样的语言,而且完全兼容Java。但是编译时得用Aspect专门的编译器,使用时必须配置Aspect的编译器,单独加入aspectj依赖是不行的。
使用AspectJ有两种方法:
-
完全使用AspectJ的语言。这语言一点也不难,和Java几乎一样,也能在AspectJ中调用Java的任何类库。AspectJ只是多了一些关键词罢了。
-
使用纯Java语言开发,然后使用AspectJ注解,简称@AspectJ。
切点表达式
1,execution
execution(<修饰符模式>?<返回类型模式><方法名模式>(<参数模式>)<异常模式>?)
注意:execution的粒度为方法,也就是说是匹配方法的
除了返回类型模式、方法名模式和参数模式外,其它项都是可选的。
-
修饰符模式: 如 public private protected
-
返回类型模型: 如 String Object 等
-
方法名模型: 如 traceOnClickView
-
参数模型: 如Params
-
异常模型: 如ClassNotFoundException
-
? 表示非必选
execution(* *..MainActivity.on*(..))
-
第一个
*
:表示方法的任意返回类型; -
*..:表示省略了MainActivity的包名,当然这里也可以写出完整包名;
-
on*表示MainActivity中所有以on开头的方法;
-
(..):表示方法的参数可以是任意类型且个数任意
精确地匹配到Person类里的eat()方法
@Before("execution(* com.zx.aop1.Person.eat())")
匹配Person类里的所有方法,可以使用通配符。
@Before("execution(* com.zx.aop1.Person.*(..))")
第一个*
表示返回值为任意类型,第二个*
表示这个类里的所有方法,()括号表示参数列表,括号里的用两个点号表示匹配任意个参数,包括0个。
2,within
within(<type name>)
接口、类名、包名
匹配com.zx.aop1.person.Student类中的所有方法
@Pointcut("within(com.zx.aop1.person.Student)")
匹配com.zx.aop1.person包及其子包中所有类中的所有方法
@Pointcut("within(com.zx.aop1.person..*)")
匹配com.zx.aop1.person.Person类及其子类的所有方法
@Pointcut("within(com.zx.aop1.person.Person+)")
匹配所有实现com.zx.aop1.person.Human接口的类的所有方法,包括接口方法和实现类的额外方法
@Pointcut("within(com.zx.aop1.person.Human+)")
3,便捷的方式获取参数
可以通过joinPoint.getArgs()的方式去拿方法参数。
@Pointcut(value = "execution(* com.zx.aop1.MainActivity.testArgs(..))")
private void pc2(){}
/**
* 通过JoinPoint连接点的方式去拿方法参数
* @param joinPoint
*/
@Before("pc2()")
public void testArgs2(JoinPoint joinPoint){
for (Object arg : joinPoint.getArgs()) {
Log.e(TAG, "testArgs2---: "+arg );
}
}
4,@annotation
匹配使用了CheckAop注解的方法
@Pointcut(value = "@annotation(checkAop)")
private void aAnnotation2(CheckAop checkAop) {
}
@After("aAnnotation2(checkAop)")
public void testaAnnotation2(CheckAop checkAop) {
Log.e(TAG, "testaAnnotation2---: "+checkAop.value() );
}
//还有这种 方式,两种方式是等价的
@Pointcut("@annotation(com.zx.aop1.CheckAop)")
private void aAnnotation1() {
}
@After("aAnnotation1()")
public void testaAnnotation(JoinPoint joinPoint) {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method method = methodSignature.getMethod();
CheckAop checkAop = method.getAnnotation(CheckAop.class);
Log.e(TAG, "testaAnnotation--: "+checkAop.value() );
}
匹配所有实现Human接口的类的所有方法且方法的第一个参数为int类型
@Pointcut("within(com.zx.aop1.person.Human+) && execution(* com.zx.aop1.person...* *(int,..))")
ProceedingJoinPoint
ProceedingJoinPoint是JoinPoint的子接口,该对象只用在@Around的切面方法中
@Around("execution(* *..MainActivity.testAOP())")
public void onActivityMethodAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
String key = proceedingJoinPoint.getSignature().toString();
Log.e(TAG, "onActivityMethodAroundFirst: before " + key + "do something");
//执行原方法
proceedingJoinPoint.proceed();
Log.e(TAG, "onActivityMethodAroundSecond: after " + key + "do something");
}
关键点在于proceed()方法,有没有发现,这个流程不就是代理模式吗?
/**
* ProceedingJoinPoint exposes the proceed(..) method in order to support around advice in @AJ aspects
*
* @author <a href="mailto:alex AT gnilux DOT com">Alexandre Vasseur</a>
*/
public interface ProceedingJoinPoint extends JoinPoint {
/**
* 执行目标方法
*
* @return
* @throws Throwable
*/
public Object proceed() throws Throwable;
/**
* 传入的新的参数去执行目标方法
*
* @param args
* @return
* @throws Throwable
*/
public Object proceed(Object[] args) throws Throwable;
}
(AspectJ)的基本原理
大白话解释:通常在做aspectj编程的时候,会定义切面@Aspect,在切面中会定义@Pointcut(切点)和Advice(通知),最后编译的时候aspectj的ajc编译器会把通知方法的逻辑代码加入到切点方法前后。当然其他JoinPoint也是可以的,比如变量访问,抛出异常等。
案例
先来看一个简单的案例,在Activity生命周期方法打印日志
@Aspect
public class AspectTest {
private static final String TAG = AspectTest.class.getSimpleName();
@Before("execution(* com.zx.aop2.MainActivity.on*(..))")
public void testLog(JoinPoint joinPoint){
MethodSignature methodSignature= (MethodSignature) joinPoint.getSignature();
Log.e(TAG, "testLog---: "+methodSignature.getMethod().getName() );
}
}
下面看一下编译后的.class文件反编译之后的效果:
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import org.aspectj.lang.JoinPoint;
import org.aspectj.runtime.reflect.Factory;
public class MainActivity extends AppCompatActivity {
public MainActivity() {
}
protected void onCreate(Bundle savedInstanceState) {
JoinPoint var2 = Factory.makeJP(ajc$tjp_0, this, this, savedInstanceState);
AspectTest.aspectOf().testLog(var2);
super.onCreate(savedInstanceState);
this.setContentView(2131427356);
}
static {
ajc$preClinit();
}
}
可以看出,我们的目标文件已经发生了变化。由于我们在切面@Aspect中是定义了在切点方法执行之前打印日志,这里的切点方法就是onCreate(),而这个方法的内容也确实如我们预想的一样,在执行onCreate()之前(也就是super之前)插入了我们的通知方法的代码。
利用aspectjx不仅可以修改项目class,还可以操作jar和aar的class。
感谢