移动架构之AOP面向切面编程

AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率

AOP切面编程的意义

当在使用面向对象编程的时候,会出现一种情况,这就是在多个类中如果需要统计某些信息,那么这些信息会出现在各个类中,但如果一旦需求改变,那么之前涉及到需求改动的地方都需要改动
这样的改动往往是巨大的,比如说在众多方法中需要统计代码段的执行时间,并打印出来,那么在需要统计的地方都需要添加相应的打印功能,然而如果现在要求将执行时间输入文件,那么每一个之前的类中都需要改写代码,这样的工作往往是很多的,这种做法也不符合设计模式的原则
把我们就需要某个方面的功能提出来与一批对象进行隔离,那么此时就有一种新的解决方案了,这便是AOP编程,AOP主要解决的问题包括:日志记录,性能统计,安全控制,事务处理,异常处理等

实例说明

使用OOP的编程思想,如果要输出一段代码执行前后的时间,那么因该是这么写的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class MainActivity extends AppCompatActivity {

private static final String TAG = "cj5785";
private SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}

public void onTestOne(View view) {
Log.d(TAG, "onTestOne: " + simpleDateFormat.format(new Date()));
long beagin = System.currentTimeMillis();
//代码逻辑
{
SystemClock.sleep(3000);
Log.d(TAG, "测试输出 1");
}
Log.d(TAG, "消耗时间: " + (System.currentTimeMillis() - beagin) + "ms");
}

public void onTestTwo(View view) {
Log.d(TAG, "onTestOne: " + simpleDateFormat.format(new Date()));
long beagin = System.currentTimeMillis();
//代码逻辑
{
SystemClock.sleep(3000);
Log.d(TAG, "测试输出 2");
}
Log.d(TAG, "消耗时间: " + (System.currentTimeMillis() - beagin) + "ms");
}
}

可以看出,如果每个类中都要求那么输出,这个工程量是巨大的,但如果这时候使用AOP,那么会省事很多

AOP编程的使用

AOP有其专有的编译工具,其是对javac的一个增强拓展编译器

Eclipse的使用:

下载aspectj,地址:http://www.eclipse.org/aspectj/downloads.php
下载aspectj的adt,地址:http://www.eclipse.org/ajdt/downloads/
安装,然后选择aspectj的编译器,把项目转换为aspectj项目

Android Studio的使用

在gradle添加如下,使项目使用aspectj编译

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
dependencies {
···
implementation 'org.aspectj:aspectjrt:1.8.13'
}

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
}
}
}
}

至于添加在modle的gradle中还是项目的gradle中就要看需求而定了

AOP实践

首先找到我们需要的切点

1
2
3
4
5
6
7
8
9
10
11
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

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

然后开始切

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import android.util.Log;

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;

import java.text.SimpleDateFormat;
import java.util.Date;

@Aspect
public class BehaviorAspect {

private static final String TAG = "cj5785";
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

//切点
@Pointcut("execution(@com.cj5785.aoptest.BehaviorTrace * *(..))")
public void annoBehavior() {
}

//切面
@Around("annoBehavior()")
public Object dealPoint(ProceedingJoinPoint point) throws Throwable {

//方法执行前
MethodSignature methodSignature = (MethodSignature) point.getSignature();
BehaviorTrace behaviorTrace = methodSignature.getMethod().getAnnotation(BehaviorTrace.class);
String contentType = behaviorTrace.value();
int type = behaviorTrace.type();
Log.d(TAG, contentType + "使用时间: " + simpleDateFormat.format(new Date()));
long beagin = System.currentTimeMillis();

//方法执行时
Object object = point.proceed();

//方法执行完成
Log.d(TAG, "消耗时间: " + (System.currentTimeMillis() - beagin) + "ms");
return object;
}
}

在需要切的地方做上标记

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import android.os.SystemClock;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;

public class MainActivity extends AppCompatActivity {

private static final String TAG = "cj5785";

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}

@BehaviorTrace(value = "onTestOne", type = 1)
public void onTestOne(View view) {
//代码逻辑
SystemClock.sleep(3000);
Log.d(TAG, "测试输出 1");
}

@BehaviorTrace(value = "onTestTwo", type = 2)
public void onTestTwo(View view) {
//代码逻辑
SystemClock.sleep(3000);
Log.d(TAG, "测试输出 2");
}
}

这样便完成了我们时间统计与具体业务的有效分离

Donate comment here