AspectJ的作用:在不侵入原有代码的基础上,增加新的代码
Join Points,简称JPoints,是AspectJ的核心思想之一,它就像一把刀,把程序的整个执行过程切成了一段段不同的部分。例如,构造方法调用、调用方法、方法执行、异常等等,这些都是Join Points,实际上,也就是你想把新的代码插在程序的哪个地方,是插在构造方法中,还是插在某个方法调用前,或者是插在某个方法中,这个地方就是Join Points,当然,不是所有地方都能给你插的,只有能插的地方,才叫Join Points。
Join Points和Pointcuts的区别实际上很难说,我也不敢说我理解的一定对,但这些都是概念上的内容,并不影响我们去使用。
Pointcuts,在我理解,实际上就是在Join Points中通过一定条件选择出我们所需要的Join Points,所以说,Pointcuts,也就是带条件的Join Points,作为我们需要的代码切入点。
又来一个Advice,Advice其实是最好理解的,也就是我们具体插入的代码,以及如何插入这些代码。我们最开始举的那个例子,里面就是使用的最简单的Advice——Before。类似的还有After、Around,我们后面来讲讲他们的区别。
@Before("execution(* android.app.Activity.on**(..))")
public void onActivityMethodBefore(JoinPoint joinPoint) throws Throwable {
}
这两个Advice应该是使用的最多的,所以,我们先来看下这两个Advice的实例,首先看下Before和After。
@Before("execution(* dawn.com.aspectjdemo.MainActivity.on*(android.os.Bundle))")
public void onActivityMethodBefore(JoinPoint joinPoint) throws Throwable {
String key = joinPoint.getSignature().toString();
Log.d(TAG, "onActivityMethodBefore: " + key);
}
@After("execution(* dawn.com.aspectjdemo.MainActivity.on*(android.os.Bundle))")
public void onActivityMethodAfter(JoinPoint joinPoint) throws Throwable {
String key = joinPoint.getSignature().toString();
Log.d(TAG, "onActivityMethodAfter: " + key);
}
经过上面的语法解释,现在看这个应该很好理解了,我们来看下编译后的类:
protected void onCreate(Bundle savedInstanceState) {
JoinPoint var2 = Factory.makeJP(ajc$tjp_0, this, this, savedInstanceState);
try {
AspectTest.aspectOf().onActivityMethodBefore(var2);
super.onCreate(savedInstanceState);
this.setContentView(2131296283);
} catch (Throwable var5) {
AspectTest.aspectOf().onActivityMethodAfter(var2);
throw var5;
}
AspectTest.aspectOf().onActivityMethodAfter(var2);
}
我们可以看见,在原始代码的基础上,增加了Before和After的代码,Log也能被正确的插入并打印出来。
Before和After其实还是很好理解的,也就是在Pointcuts之前和之后,插入代码,那么Around呢,从字面含义上来讲,也就是在方法前后各插入代码,是的,他包含了Before和After的全部功能,代码如下:
@Around("execution(* dawn.com.aspectjdemo.MainActivity.testAOP())")
public void onActivityMethodAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
String key = proceedingJoinPoint.getSignature().toString();
Log.d(TAG, "onActivityMethodAroundFirst: " + key);
proceedingJoinPoint.proceed();
Log.d(TAG, "onActivityMethodAroundSecond: " + key);
}
其中,proceedingJoinPoint.proceed()代表执行原始的方法,在这之前、之后,都可以进行各种逻辑处理。
原始代码:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
testAOP();
}
public void testAOP() {
Log.d("lzx", "testAOP");
}
}
日志打印:
04-17 13:50:30.584 8086-8086/dawn.com.aspectjdemo D/lzx: onActivityMethodAroundFirst: void dawn.com.aspectjdemo.MainActivity.testAOP()
04-17 13:50:30.584 8086-8086/dawn.com.aspectjdemo D/lzx: testAOP
04-17 13:50:30.584 8086-8086/dawn.com.aspectjdemo D/lzx: onActivityMethodAroundSecond: void dawn.com.aspectjdemo.MainActivity.testAOP()
自定义Pointcuts可以让我们更加精确的切入一个或多个指定的切入点。
首先,我们需要自定义一个注解类,例如——DebugTool.java:
@Retention(RetentionPolicy.CLASS)
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD})
public @interface DebugTool {
}
然后在需要插入代码的地方使用这个注解:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
testAOP();
}
@DebugTool
public void testAOP() {
Log.d("xuyisheng", "testAOP");
}
}
最后,我们来创建自己的切入文件。
@Pointcut("execution(@dawn.com.aspectjdemo.DebugTool * *(..))")
public void DebugToolMethod(){}
@Before("DebugToolMethod()")
public void onDebugToolMethodBefore(JoinPoint joinPoint) throws Throwable{
String key =joinPoint.getSignature().toString();
Log.d(TAG, "onDebugToolMethodBefore: " + key);
}
先定义Pointcut,并申明要监控的方法名,最后,在Before或者其它Advice里面添加切入代码,即可完成切入。
日志打印:
04-17 14:04:50.371 9719-9719/dawn.com.aspectjdemo D/lzx: onDebugToolMethodBefore: void dawn.com.aspectjdemo.MainActivity.testAOP()
04-17 14:04:50.371 9719-9719/dawn.com.aspectjdemo D/lzx: testAOP
通过这种方式,我们可以非常方便的监控指定的Pointcut,从而增加监控的粒度。
在AspectJ的切入点表达式中,我们前面都是使用的execution,实际上,还有一种类型——call,那么这两种语法有什么区别呢?
execution是在被切入的方法中,call是在调用被切入的方法前或者后。
对于Call来说:
Call(Before)
Pointcut{
Pointcut Method
}
Call(After)
对于Execution来说:
Pointcut{
execution(Before)
Pointcut Method
execution(After)
}
除了前面提到的call和execution,比较常用的还有一个withincode。这个语法通常来进行一些切入点条件的过滤,作更加精确的切入控制。我们可以参考下面这个例子:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
testAOP1();
testAOP2();
}
public void testAOP() {
Log.d("xuyisheng", "testAOP");
}
public void testAOP1() {
testAOP();
}
public void testAOP2() {
testAOP();
}
}
testAOP1()和testAOP2()都调用了testAOP()方法,但是,现在想在testAOP2()方法调用testAOP()方法的时候,才切入代码,那么这个时候,就需要使用到Pointcut和withincode组合的方式,来精确定位切入点。
// 在testAOP2()方法内
@Pointcut("withincode(* dawn.com.aspectjdemo.MainActivity.testAOP2(..))")
public void invokeAOP2() {
}
// 调用testAOP()方法的时候
@Pointcut("call(* dawn.com.aspectjdemo.MainActivity.testAOP(..))")
public void invokeAOP() {
}
// 同时满足前面的条件,即在testAOP2()方法内调用testAOP()方法的时候才切入
@Pointcut("invokeAOP() && invokeAOP2()")
public void invokeAOPOnlyInAOP2(){
}
@Before("invokeAOPOnlyInAOP2()")
public void beforeInvokeAOPOnlyInAOP2(JoinPoint joinPoint) {
String key = joinPoint.getSignature().toString();
Log.d(TAG, "onDebugToolMethodBefore: " + key);
}
我们再来看下编译后的代码:
public class MainActivity extends AppCompatActivity {
public MainActivity() {
}
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setContentView(2131296283);
this.testAOP1();
this.testAOP2();
}
public void testAOP() {
Log.d("lzx", "testAOP");
}
public void testAOP1() {
this.testAOP();
}
public void testAOP2() {
JoinPoint var1 = Factory.makeJP(ajc$tjp_0, this, this);
AspectTest.aspectOf().beforeInvokeAOPOnlyInAOP2(var1);
this.testAOP();
}
static {
ajc$preClinit();
}
}
我们可以看见,只有在testAOP2()方法中被插入了代码,这就做到了精确条件的插入。
AfterThrowing是一个比较少见的Advice,他用于处理程序中未处理的异常,记住,这点很重要,是未处理的异常,具体原因,我们等会看反编译出来的代码就知道了。我们随手写一个异常,代码如下:
public void testAOP() {
View view = null;
view.animate();
}
然后使用AfterThrowing来进行AOP代码的编写:
@AfterThrowing(pointcut = "execution(* dawn.com.aspectjdemo.*.*(..))",throwing = "exception")
public void catchExceptionMethod(Exception exception) {
String message = exception.toString();
Log.d(TAG, "catchExceptionMethod: " + message);
}
这段代码很简单,同样是使用我们前面类似的表达式,但是这里是为了处理异常,所以,使用了.来进行通配,在异常中,我们执行一行日志,编译好的代码如下:
protected void onCreate(Bundle savedInstanceState) {
try {
super.onCreate(savedInstanceState);
this.setContentView(2131296283);
this.testAOP();
} catch (Exception var3) {
AspectTest.aspectOf().catchExceptionMethod(var3);
throw var3;
}
}
public void testAOP() {
try {
View view = null;
((View)view).animate();
} catch (Exception var3) {
AspectTest.aspectOf().catchExceptionMethod(var3);
throw var3;
}
}
public void testAOP1() {
try {
this.testAOP();
} catch (Exception var2) {
AspectTest.aspectOf().catchExceptionMethod(var2);
throw var2;
}
}
public void testAOP2() {
try {
JoinPoint var1 = Factory.makeJP(ajc$tjp_0, this, this);
AspectTest.aspectOf().beforeInvokeAOPOnlyInAOP2(var1);
this.testAOP();
} catch (Exception var4) {
AspectTest.aspectOf().catchExceptionMethod(var4);
throw var4;
}
}
我们可以看见包下的所有方法都被加上了try catch,同时,在catch中,被插入了我们切入的代码,但是最后,他依然会throw e,也就是说,这个异常已经会被抛出去,崩溃依旧是会发生的。同时,如果你的原始代码中已经try catch了,那么同样也无法处理