Aspect的使用及其編譯器的原理

一、為什么要用到Aspect?

相信很多做過(guò)Web的同學(xué)對(duì)AspectJ都不陌生,Spring的AOP就是基于它而來(lái)的。最近在研究Android客戶端記錄方法的耗時(shí),需要在把每個(gè)方法執(zhí)行耗時(shí)記錄Log,然后上傳到服務(wù)器里。

如果在一個(gè)大型的項(xiàng)目當(dāng)中,使用手動(dòng)修改源碼的方式來(lái)達(dá)到記錄、監(jiān)控的目的,第一,需要插入許多重復(fù)代碼(打印日志,監(jiān)控方法執(zhí)行時(shí)間),代碼無(wú)法復(fù)用;第二,修改的成本太高,處處需要手動(dòng)修改(分分鐘累死、眼花)。

沒(méi)錯(cuò)!這時(shí)你可以選擇AspectJ輕松地來(lái)完成這個(gè)任務(wù)。

QQ圖片20180307112001.png

二、什么是AspectJ?

AspectJ 意思就是Java的Aspect,Java的AOP。它是一個(gè)代碼編譯器,在Java編譯器的基礎(chǔ)上增加了一些它自己的關(guān)鍵字識(shí)別和編譯方法。它在編譯期將開(kāi)發(fā)者編寫(xiě)的Aspect程序編織到目標(biāo)程序中,對(duì)目標(biāo)程序作了重構(gòu),目的就是建立目標(biāo)程序與Aspect程序的連接(耦合,獲得對(duì)方的引用(獲得的是聲明類(lèi)型,不是運(yùn)行時(shí)類(lèi)型)和上下文信息),從而達(dá)到AOP的目的。

QQ圖片20180307113225.png

三、AspectJ的使用

1.導(dǎo)入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.首先在項(xiàng)目中創(chuàng)建一個(gè)注解類(lèi)

/**
 * Author:pengzhe on 2018/3/7 09:52
 * 描述: 性能測(cè)試
 */

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


@interface 表示是一個(gè)注解類(lèi),@Target 表示該注解所作用的對(duì)象(ElementType.Method 表示這個(gè)注解是作用在方法上的),@Retention 表示該注解關(guān)聯(lián)的時(shí)機(jī),即在何時(shí)進(jìn)行關(guān)聯(lián)(RetentionPolicy.RUNTIME 表示這個(gè)注解在運(yùn)行時(shí)進(jìn)行關(guān)聯(lián))。

  1. 創(chuàng)建切面
/**
 * 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", "性能檢測(cè)被執(zhí)行了....");
        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類(lèi)中的%s方法執(zhí)行花費(fèi)了%d ms", funName, className, methodName, System.currentTimeMillis() - time));
        return result;
    }
}

@Aspect 聲明該類(lèi) 屬于一個(gè)切面,@Pointcut 表示切入點(diǎn),通過(guò)切入點(diǎn)來(lái)獲取目標(biāo)對(duì)象,通過(guò)("execution(@test.pz.com.annotation.TestBehaviorTrace * *(..))"來(lái)截取所有被打上@TestBehaviorTrace 標(biāo)簽的對(duì)象。@Around 表示 對(duì) "methodAnnotatedwithBehaviorTrace()" 所截取的對(duì)象方法在執(zhí)行前和執(zhí)行后分別插入新的代碼,并原方法進(jìn)行修改替換。除了@Around以外,還有其他注解,例如 @Before 表示在對(duì)象方法的執(zhí)行前進(jìn)行干預(yù),@After 表示在對(duì)象方法的執(zhí)行后進(jìn)行干預(yù)。ProceedingJoinPoint joinPoint 表示被截取,被干預(yù)的對(duì)象方法,使用 joinPoint.proceed() 對(duì)該對(duì)象方法進(jìn)行執(zhí)行,joinPoint.proceed()的返回值就是該方法的返回值。

上述代碼里,在 joinPoint.proceed()執(zhí)行前,獲取了系統(tǒng)時(shí)間time ,在執(zhí)行后使用 System.currentTimeMillis() - time 來(lái)獲取方法所消耗的時(shí)間,這樣就完成了對(duì)方法耗時(shí)的檢測(cè)。 joinPoint.getSignature() 是可以獲取方法的簽名,通過(guò)方法簽名 methodSignature.getDeclaringType().getSimpleName()可以拿到類(lèi)名,再通過(guò)Java的反射機(jī)制,就可以拿到這個(gè)類(lèi)的屬性值,做很多操作。

Around替代原理:目標(biāo)方法體被Around方法替換,原方法重新生成,名為XXX_aroundBody(),如果要調(diào)用原方法需要在AspectJ程序的Around方法體內(nèi)調(diào)用joinPoint.proceed()還原方法執(zhí)行,是這樣達(dá)到替換原方法的目的。達(dá)到這個(gè)目的需要雙方互相引用,橋梁便是Aspect類(lèi),目標(biāo)程序插入了Aspect類(lèi)所在的包獲取引用。AspectJ通過(guò)在目標(biāo)類(lèi)里面加入Closure(閉包)類(lèi),該類(lèi)構(gòu)造函數(shù)包含了目標(biāo)類(lèi)實(shí)例、目標(biāo)方法參數(shù)、JoinPoint對(duì)象等信息,同時(shí)該類(lèi)作為切點(diǎn)原方法的執(zhí)行代理,該閉包通過(guò)Aspect類(lèi)調(diào)用Around方法傳入Aspect程序。這樣便達(dá)到了關(guān)聯(lián)的目的,便可以在Aspect程序中監(jiān)控和修改目標(biāo)程序。

  1. 在Activity中為所要修改的方法打上標(biāo)簽
/**
 * 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("性能測(cè)試")
    public void mTest(View view) {
        SystemClock.sleep(new Random().nextInt(2000));
    }

}

運(yùn)行該程序,mTest 方法執(zhí)行后,該方法的耗時(shí)會(huì)被自動(dòng)記錄到Log,我們可以通過(guò)在TestBehaviorTraceAspect 切面上編程,完成更為復(fù)雜的邏輯,比如將Log保存到數(shù)據(jù)庫(kù)或者本地文件,在有互聯(lián)網(wǎng)時(shí),上傳到我們的服務(wù)器后臺(tái)。

四、 參考資料: Android基于AOP的非侵入式監(jiān)控之——AspectJ實(shí)戰(zhàn)
http://blog.csdn.net/woshimalingyi/article/details/51476559

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容