AspectJ入门

AspectJ的作用:在不侵入原有代码的基础上,增加新的代码

AspectJ之Join Points

Join Points,简称JPoints,是AspectJ的核心思想之一,它就像一把刀,把程序的整个执行过程切成了一段段不同的部分。例如,构造方法调用、调用方法、方法执行、异常等等,这些都是Join Points,实际上,也就是你想把新的代码插在程序的哪个地方,是插在构造方法中,还是插在某个方法调用前,或者是插在某个方法中,这个地方就是Join Points,当然,不是所有地方都能给你插的,只有能插的地方,才叫Join Points。

AspectJ之Pointcuts

Join Points和Pointcuts的区别实际上很难说,我也不敢说我理解的一定对,但这些都是概念上的内容,并不影响我们去使用。

Pointcuts,在我理解,实际上就是在Join Points中通过一定条件选择出我们所需要的Join Points,所以说,Pointcuts,也就是带条件的Join Points,作为我们需要的代码切入点。

AspectJ之Advice

又来一个Advice,Advice其实是最好理解的,也就是我们具体插入的代码,以及如何插入这些代码。我们最开始举的那个例子,里面就是使用的最简单的Advice——Before。类似的还有After、Around,我们后面来讲讲他们的区别。

AspectJ之切点语法

@Before("execution(* android.app.Activity.on**(..))")
public void onActivityMethodBefore(JoinPoint joinPoint) throws Throwable {
}
  • @Before: Advice,也就是具体的切入点
  • execution: 处理Join Point 的类型,例如call,execution
  • (* android.app.Activity.on**(..)) :这个是最重要的表达式,第一个* 表示返回值为任意类型,后面这个就是典型的包名路径,其中可以包含来通配,几个没有关系。同时,这里可以通过『&&、||、!』来进行条件组合。()代表这个方法的参数,你可以指定类型,例如android.os.Bundle,或者(..)这样来代表任意类型、任意个数的参数。
  • public void onActivityMethodBefore:实际切入的代码

AspectJ实例

Before、After

这两个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也能被正确的插入并打印出来。

Around

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

自定义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,从而增加监控的粒度。

call和execution

在AspectJ的切入点表达式中,我们前面都是使用的execution,实际上,还有一种类型——call,那么这两种语法有什么区别呢?

execution是在被切入的方法中,call是在调用被切入的方法前或者后。

对于Call来说:

CallBefore)
Pointcut{
    Pointcut Method
}
CallAfter

对于Execution来说:

Pointcut{
  execution(Before)
    Pointcut Method
  execution(After)
}

切入点过滤与withincode

除了前面提到的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

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了,那么同样也无法处理

你可能感兴趣的:(Android必备技)