Aspect的使用及其编译器的原理

2018-03-07  本文已影响249人  Veneto_2022

一、为什么要用到Aspect?

相信很多做过Web的同学对AspectJ都不陌生,Spring的AOP就是基于它而来的。最近在研究Android客户端记录方法的耗时,需要在把每个方法执行耗时记录Log,然后上传到服务器里。

如果在一个大型的项目当中,使用手动修改源码的方式来达到记录、监控的目的,第一,需要插入许多重复代码(打印日志,监控方法执行时间),代码无法复用;第二,修改的成本太高,处处需要手动修改(分分钟累死、眼花)。

没错!这时你可以选择AspectJ轻松地来完成这个任务。

QQ图片20180307112001.png

二、什么是AspectJ?

AspectJ 意思就是Java的Aspect,Java的AOP。它是一个代码编译器,在Java编译器的基础上增加了一些它自己的关键字识别和编译方法。它在编译期将开发者编写的Aspect程序编织到目标程序中,对目标程序作了重构,目的就是建立目标程序与Aspect程序的连接(耦合,获得对方的引用(获得的是声明类型,不是运行时类型)和上下文信息),从而达到AOP的目的。

QQ图片20180307113225.png

三、AspectJ的使用

1.导入aspectjrt.jar 以及配置 build.gradle

apply plugin: 'com.android.application'

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath 'org.aspectj:aspectjtools:1.8.8'
        classpath 'org.aspectj:aspectjweaver:1.8.8'
    }
}

android {
    compileSdkVersion 24
    buildToolsVersion "25.0.0"
    defaultConfig {
        applicationId "test.pz.com.annotation"
        minSdkVersion 14
        targetSdkVersion 24
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    compile 'com.android.support:appcompat-v7:24.2.1'
    compile 'com.android.support.constraint:constraint-layout:1.0.2'
    testCompile 'junit:junit:4.12'
    compile files('libs/aspectjrt.jar')

}

import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main

final def log = project.logger
final def variants = project.android.applicationVariants

variants.all { variant ->
    if (!variant.buildType.isDebuggable()) {
        log.debug("Skipping non-debuggable build type '${variant.buildType.name}'.")
        return;
    }

    JavaCompile javaCompile = variant.javaCompile
    javaCompile.doLast {
        String[] args = ["-showWeaveInfo",
                         "-1.8",
                         "-inpath", javaCompile.destinationDir.toString(),
                         "-aspectpath", javaCompile.classpath.asPath,
                         "-d", javaCompile.destinationDir.toString(),
                         "-classpath", javaCompile.classpath.asPath,
                         "-bootclasspath", project.android.bootClasspath.join(File.pathSeparator)]
        log.debug "ajc args: " + Arrays.toString(args)

        MessageHandler handler = new MessageHandler(true);
        new Main().run(args, handler);
        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:
                    log.warn message.message, message.thrown
                    break;
                case IMessage.INFO:
                    log.info message.message, message.thrown
                    break;
                case IMessage.DEBUG:
                    log.debug message.message, message.thrown
                    break;
            }
        }
    }
}


2.首先在项目中创建一个注解类

/**
 * Author:pengzhe on 2018/3/7 09:52
 * 描述: 性能测试
 */

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestBehaviorTrace {
    String value();
}


@interface 表示是一个注解类,@Target 表示该注解所作用的对象(ElementType.Method 表示这个注解是作用在方法上的),@Retention 表示该注解关联的时机,即在何时进行关联(RetentionPolicy.RUNTIME 表示这个注解在运行时进行关联)。

  1. 创建切面
/**
 * Author:pengzhe on 2018/3/7 09:55
 * 描述:
 */

@Aspect
public class TestBehaviorTraceAspect {

    @Pointcut("execution(@test.pz.com.annotation.TestBehaviorTrace * *(..))")
    public void methodAnnotatedwithBehaviorTrace() {
    }

    @Around("methodAnnotatedwithBehaviorTrace()")
    public Object weaveJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable {
        Log.d("pengzhe", "性能检测被执行了....");
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        String methodName = methodSignature.getMethod().getName();
        String className = methodSignature.getDeclaringType().getSimpleName();
        String funName = methodSignature.getMethod().getAnnotation(TestBehaviorTrace.class).value();
        long time = System.currentTimeMillis();
        Object result = joinPoint.proceed();
        Log.d("pengzhe", String.format("功能: %s , %s类中的%s方法执行花费了%d ms", funName, className, methodName, System.currentTimeMillis() - time));
        return result;
    }
}

@Aspect 声明该类 属于一个切面,@Pointcut 表示切入点,通过切入点来获取目标对象,通过("execution(@test.pz.com.annotation.TestBehaviorTrace * *(..))"来截取所有被打上@TestBehaviorTrace 标签的对象。@Around 表示 对 "methodAnnotatedwithBehaviorTrace()" 所截取的对象方法在执行前和执行后分别插入新的代码,并原方法进行修改替换。除了@Around以外,还有其他注解,例如 @Before 表示在对象方法的执行前进行干预,@After 表示在对象方法的执行后进行干预。ProceedingJoinPoint joinPoint 表示被截取,被干预的对象方法,使用 joinPoint.proceed() 对该对象方法进行执行,joinPoint.proceed()的返回值就是该方法的返回值。

上述代码里,在 joinPoint.proceed()执行前,获取了系统时间time ,在执行后使用 System.currentTimeMillis() - time 来获取方法所消耗的时间,这样就完成了对方法耗时的检测。 joinPoint.getSignature() 是可以获取方法的签名,通过方法签名 methodSignature.getDeclaringType().getSimpleName()可以拿到类名,再通过Java的反射机制,就可以拿到这个类的属性值,做很多操作。

Around替代原理:目标方法体被Around方法替换,原方法重新生成,名为XXX_aroundBody(),如果要调用原方法需要在AspectJ程序的Around方法体内调用joinPoint.proceed()还原方法执行,是这样达到替换原方法的目的。达到这个目的需要双方互相引用,桥梁便是Aspect类,目标程序插入了Aspect类所在的包获取引用。AspectJ通过在目标类里面加入Closure(闭包)类,该类构造函数包含了目标类实例、目标方法参数、JoinPoint对象等信息,同时该类作为切点原方法的执行代理,该闭包通过Aspect类调用Around方法传入Aspect程序。这样便达到了关联的目的,便可以在Aspect程序中监控和修改目标程序。

  1. 在Activity中为所要修改的方法打上标签
/**
 * Author:pengzhe on 2018/3/7 09:48
 * 描述:
 */
public class NextActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_next);
    }


    @TestBehaviorTrace("性能测试")
    public void mTest(View view) {
        SystemClock.sleep(new Random().nextInt(2000));
    }

}

运行该程序,mTest 方法执行后,该方法的耗时会被自动记录到Log,我们可以通过在TestBehaviorTraceAspect 切面上编程,完成更为复杂的逻辑,比如将Log保存到数据库或者本地文件,在有互联网时,上传到我们的服务器后台。

四、 参考资料: Android基于AOP的非侵入式监控之——AspectJ实战
http://blog.csdn.net/woshimalingyi/article/details/51476559

上一篇下一篇

猜你喜欢

热点阅读