美文网首页
Aspect的使用及其编译器的原理

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

作者: Veneto_2022 | 来源:发表于2018-03-07 16:26 被阅读249次

    一、为什么要用到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

    相关文章

      网友评论

          本文标题:Aspect的使用及其编译器的原理

          本文链接:https://www.haomeiwen.com/subject/klcjfftx.html