面向切面编程(AOP,Aspect-oriented programming)需要把程序逻辑分解成『关注点』(concerns,功能的内聚区域)。这意味着,在 AOP 中,我们不需要显式的修改就可以向代码中添加可执行的代码块。这种编程范式假定『横切关注点』(cross-cutting concerns,多处代码中需要的逻辑,但没有一个单独的类来实现)应该只被实现一次,且能够多次注入到需要该逻辑的地方。
代码注入是 AOP 中的重要部分:它在处理上述提及的横切整个应用的『关注点』时很有用,例如日志或者性能监控。这种方式,并不如你所想的应用甚少,相反的,每个程序员都可以有使用这种注入代码能力的场景,这样可以避免很多痛苦和无奈。
AOP 是一种已经存在了很多年的编程范式。我发现把它应用到 Android 开发中也很有用。经过一番调研后,我认为我们用它可以获得很多好处和有用的东西。
那么...我们何时何地应用AOP呢?
一些示例的 cross-cutting concerns 如下:
- 日志
- 持久化
- 性能监控
- 数据校验
- 缓存
这里我们重点使用AspectJ完成一个统计方法执行耗时的功能。
1.开发环境:
Android Studio 3.1
Java 1.7
Android Plugin 2.3.3
Gradle 3.3
2.环境配置
项目build.gradle文件
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.3.3'
classpath 'org.aspectj:aspectjtools:1.9.0'
classpath 'org.aspectj:aspectjweaver:1.9.0'
}
}
allprojects {
repositories {
jcenter()
}
}
Module中的build.gradle文件
apply plugin: 'com.android.application'
dependencies {
compile 'org.aspectj:aspectjrt:1.9.0'
}
#这里要加入下面这段,原因是要使用它来参与编译
final def log = project.logger
final def variants = project.android.applicationVariants
import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main
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.7",
"-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;
}
}
}
}
到这里环境配置结束
3.功能代码
我们的目标是监控方法执行时间:
3.1创建DebugTrace.java
这是一个注解类,是为了标记我们要追踪的方法
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.CLASS)
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD})
public @interface DebugTrace {
}
3.2创建TimeWatcher.java
这个类的功能是记录时间
import java.util.concurrent.TimeUnit;
public class TimeWatcher {
private long startTime;
private long endTime;
private long elapsedTime;
public TimeWatcher() {
//empty
}
private void reset() {
startTime = 0;
endTime = 0;
elapsedTime = 0;
}
public void start() {
reset();
startTime = System.nanoTime();
}
public void stop() {
if (startTime != 0) {
endTime = System.nanoTime();
elapsedTime = endTime - startTime;
} else {
reset();
}
}
public long getTotalTimeMillis() {
return (elapsedTime != 0) ? TimeUnit.NANOSECONDS.toMillis(endTime - startTime) : 0;
}
}
3.3创建TraceAspect.java
这个类是核心类,首先在类上使用@Aspect
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
@Aspect
public class TraceAspect {
private static final String POINTCUT_METHOD = "execution(@leon.training.designpattern.structure.proxy.dynamic.aspectJ.DebugTrace * *(..))";
private static final String POINTCUT_CONSTRUCTOR = "execution(@leon.training.designpattern.structure.proxy.dynamic.aspectJ.DebugTrace *.new(..))";
@Pointcut(POINTCUT_METHOD)
public void methodAnnotatedWithDebugTrace() {
}
@Pointcut(POINTCUT_CONSTRUCTOR)
public void constructorAnnotatedDebugTrace() {
}
@Around("methodAnnotatedWithDebugTrace() || constructorAnnotatedDebugTrace()")
public Object weaveJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
String className = methodSignature.getDeclaringType().getSimpleName();
String methodName = methodSignature.getName();
final TimeWatcher stopWatch = new TimeWatcher();
stopWatch.start();
Object result = joinPoint.proceed();
stopWatch.stop();
Log.d("TAG", buildLogMessage(methodName, stopWatch.getTotalTimeMillis()));
return result;
}
/**
* Create a log message.
*
* @param methodName A string with the method name.
* @param methodDuration Duration of the method in milliseconds.
* @return A string representing message.
*/
private static String buildLogMessage(String methodName, long methodDuration) {
StringBuilder message = new StringBuilder();
message.append("方法 --> ");
message.append(methodName);
message.append(" 耗时");
message.append(" --> ");
message.append("[");
message.append(methodDuration);
message.append("ms");
message.append("]");
return message.toString();
}
}
具体原理和aspectj的使用方法不在这里讨论
总体来说使用简单,对原生代码侵入性小