一、為什么要用到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ù)。

二、什么是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的目的。

三、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))。
- 創(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)程序。
- 在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)