一、为什么要用到Aspect?
相信很多做过Web的同学对AspectJ都不陌生,Spring的AOP就是基于它而来的。最近在研究Android客户端记录方法的耗时,需要在把每个方法执行耗时记录Log,然后上传到服务器里。
如果在一个大型的项目当中,使用手动修改源码的方式来达到记录、监控的目的,第一,需要插入许多重复代码(打印日志,监控方法执行时间),代码无法复用;第二,修改的成本太高,处处需要手动修改(分分钟累死、眼花)。
没错!这时你可以选择AspectJ轻松地来完成这个任务。
![](https://img.haomeiwen.com/i3589197/d1444714cdf79c7d.png)
二、什么是AspectJ?
AspectJ 意思就是Java的Aspect,Java的AOP。它是一个代码编译器,在Java编译器的基础上增加了一些它自己的关键字识别和编译方法。它在编译期将开发者编写的Aspect程序编织到目标程序中,对目标程序作了重构,目的就是建立目标程序与Aspect程序的连接(耦合,获得对方的引用(获得的是声明类型,不是运行时类型)和上下文信息),从而达到AOP的目的。
![](https://img.haomeiwen.com/i3589197/3f520d8f182c20ab.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 表示这个注解在运行时进行关联)。
- 创建切面
/**
* 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程序中监控和修改目标程序。
- 在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)
网友评论