AOP(面向切面编程)

AOP之AspectJ - 代码注入

2017-05-26  本文已影响276人  everlastxgb

AOP之AspectJ - 代码注入


[TOC]

一、AOP简介

1.1 什么是AOP编程

AOP是Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。

AOP编程是一种区别OOP编程的概念,从切面的角度看待问题,是函数式编程的一种衍生范型。在AOP中,我们不需要显式的修改就可以向代码中添加可执行的代码块,有效的保证了业务逻辑的各个部分的隔离,降低耦合度,提高程序的可重用性,同时提高了开发的效率。

OOP的思想让我们把功能或问题模块化,每个模块有自己的职责和使命。相比较,AOP让我们在保持开发模块隔离的同时可以将一些需要横跨多个模块的代码嵌入其中,把涉及到众多模块的某一类问题进行统一管理。

AOP-横跨.png

1.2 使用场景

1.3 工具和库

目前已有不少的工具和库能帮助我们方便使用AOP。

二、AspectJ

2.1 简介

AspectJ是一个面向切面的框架,它扩展了Java语言。AspectJ定义了AOP语法,它有一个专门的编译器用来生成遵守Java字节编码规范的Class文件,支持静态编译和动态编译。

使用AspectJ编码更为简洁,API简单易用。个人觉得,在Android开发中,是实现AOP的首选。

2.2 一些专业术语

下面引用《Aspect Oriented Programming in Android》中的两张图来帮助我们更好地理解这些概念:

图1:

AspectOrientedProgramming

图2:

AspectWeaving

2.3 基础知识

继续向下阅读之前,你可能需要先了解一些基础知识以便更好地理解。由于篇幅有限,无法扩展详解,可以根据以下列出的内容先行了解。

往后的内容我们将针对AspectJ的具体使用和如何根据使用场景做成插件来展开探讨。

2.4 AspectJ使用配置

(Android Studio Gradle)

1.添加 dependencies classpath

classpath 'org.aspectj:aspectjtools:1.8.10'

2.添加 dependencies compile

compile 'org.aspectj:aspectjrt:1.8.10'

3.build.gradle添加task命令
(若为多Module则每个Module对应的gradle都需添加)

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 // when in application module
// final def variants = project.android.libraryVariants // when in library module
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.5",
                         "-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.1一个简单的示例

本节将演示利用AspectJ在编译期向标有注解的方法织入两行代码,分别在执行前和执行后,并输出log。

工程结构如下:

0.添加相关依赖和声明

gradle中的配置请参考上面 2.4 AspectJ使用配置

1.定义一个注解

@Retention(RetentionPolicy.CLASS)
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.CONSTRUCTOR})
public @interface GMonitor {
}

2.定义代码注入Aspect类

/**
 * Aspect representing the cross cutting-concern: Method and Constructor Tracing.
 */
@Aspect
public class GodMonitor {

  private static final String POINTCUT_METHOD =
          "execution(@com.kido.godmonitor.weaving.GMonitor * *(..))"; // 通过GMonitor注解的方法

  private static final String POINTCUT_CONSTRUCTOR =
          "execution(@com.kido.godmonitor.weaving.GMonitor *.new(..))"; // 通过GMonitor注解的构造函数

  @Pointcut(POINTCUT_METHOD)
  public void methodAnnotatedWithDebugTrace() {}

  @Pointcut(POINTCUT_CONSTRUCTOR)
  public void constructorAnnotatedDebugTrace() {}

  @Around("methodAnnotatedWithDebugTrace() || constructorAnnotatedDebugTrace()") // 筛选出所有通过GMonitor注解的方法和构造函数
  public Object weaveJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable {
    MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
    String className = methodSignature.getDeclaringType().getSimpleName();
    String methodName = methodSignature.getName();

    DebugLog.log(className, buildLogMessage(methodName, " before execution "));
    Object result = joinPoint.proceed(); // 注解所在的方法/构造函数的执行的地方
    DebugLog.log(className, buildLogMessage(methodName, " after execution "));

    return result;
  }

  /**
   * Create a log message.
   *
   * @param methodName A string with the method name.
   * @param info Extra info.
   * @return A string representing message.
   */
  private static String buildLogMessage(String methodName, String info) {
    StringBuilder message = new StringBuilder();
    message.append("GodMonitor --> ");
    message.append(methodName);
    message.append(" --> ");
    message.append("[");
    message.append(info);
    message.append("]");

    return message.toString();
  }
}

3.在MainActivity中测试

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "kido";

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

        findViewById(R.id.button1).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                doAction1();
            }
        });
        findViewById(R.id.button2).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                doAction2();
            }
        });
    }

    @GMonitor
    private void doAction1() {
        Log.d(TAG, "you do the action111111.");
    }

    @GMonitor
    private void doAction2() {
        Log.d(TAG, "you do the action222222.");
    }
}

4.运行结果

运行程序点击按钮,可以看到在方法前后成功输出了我们织入的代码片段输出的log。

com.kido.godmonitor D/MainActivity: GodMonitor --> doAction1 --> [ before execution ]
com.kido.godmonitor D/kido: you do the action111111.
com.kido.godmonitor D/MainActivity: GodMonitor --> doAction1 --> [ after execution ]

com.kido.godmonitor D/MainActivity: GodMonitor --> doAction2 --> [ before execution ]
com.kido.godmonitor D/kido: you do the action222222.
com.kido.godmonitor D/MainActivity: GodMonitor --> doAction2 --> [ after execution ]

5.反编译看生成的class

反编译看生成的class文件,可以看到在标有注解的地方中被织入了aspect的相关代码:

    @GMonitor
    private void doAction1() {
        JoinPoint var1 = Factory.makeJP(ajc$tjp_0, this, this);
        GodMonitor var10000 = GodMonitor.aspectOf();
        Object[] var2 = new Object[]{this, var1};
        var10000.weaveJoinPoint((new MainActivity$AjcClosure1(var2)).linkClosureAndJoinPoint(69648));
    }

6.本例源码地址

GodMonitor-pre

3.2 一个简单的示例(后续)

上一节我们利用AspectJ实现了一个简单的代码注入示例,但是我们会发现,我们在Module的build.gradle需要添加一大串的AspectJ的task命令,如果一旦Module很多,那么将会异常繁琐。
那么,有什么简单快捷的替代方案吗?答案肯定是有的,我们可以自定义gradle plugin,将这部分逻辑移到plugin中实现,那么在需要的Module处我们直接声明引用plugin即可。

3.2.1 自定义plugin

添加sub module,名为godmonitor-plugin,用于使用Groovy开发我们对应的插件。那么,工程结构就会相应变成如下:

1.GodMonitorPlugin.groovy


class GodMonitorPlugin implements Plugin<Project> {
  @Override void apply(Project project) {
    def hasApp = project.plugins.withType(AppPlugin)
    def hasLib = project.plugins.withType(LibraryPlugin)
    if (!hasApp && !hasLib) {
      throw new IllegalStateException("'android' or 'android-library' plugin required.")
    }

    final def log = project.logger
    final def variants
    if (hasApp) {
      variants = project.android.applicationVariants
    } else {
      variants = project.android.libraryVariants
    }

    project.dependencies {
      compile 'com.kido.godmonitor:godmonitor-runtime:0.0.1'
      // TODO this should come transitively
      compile 'org.aspectj:aspectjrt:1.8.10'
      compile 'com.kido.godmonitor:godmonitor-annotations:0.0.1'
    }

    project.extensions.create('godmonitor', GodMonitorExtension)

    variants.all { variant ->
      if (!project.godmonitor.enabled) {
        log.debug("GodMonitor is not disabled.")
        return;
      }

      JavaCompile javaCompile = variant.javaCompile
      javaCompile.doLast {
        String[] args = [
            "-showWeaveInfo",
            "-1.5",
            "-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;
          }
        }
      }
    }
  }
}

编译成插件之后,你会发现你在example中只需引用如下即可:

classpath 'com.kido.godmonitor:godmonitor-plugin:0.0.1'
...
apply plugin: 'godmonitor'

3.2.2 发布到Maven

  1. 添加发布脚本gradle-mvn-push.gradle。
  2. 在gradle.properties中设置发布信息。
  3. 在插件工程build.gradle中引用脚本。
  4. 使用gradle的uploadArchives命令上传发布。

(详情可参见项目源码)

3.2.3 一些说明

maven { url 'http://100.84.197.220:8089/repository/android-releases/' }
...
classpath 'com.kido.godmonitor:godmonitor-plugin:0.0.1'
...
apply plugin: 'godmonitor'
    @GMonitor
    private void doAction1() {
        Log.d(TAG, "you do the action111111.");
    }

    @GMonitor
    private void doAction2() {
        Log.d(TAG, "you do the action222222.");
    }

四、小结

AOP编程在进行用户行为统计方面是一种非常可靠的解决方案,避免了直接在业务代码中进行埋点,另外,它在性能监控、数据采集等方面也有着广泛的应用。AspectJ作为AOP编程的一个实现框架,方便易用,主要关键在于掌握它的pointcut的语法。在实际使用中,我们可以根据具体场景将我们的AOP模块封装成插件的方式,隐藏实现细节,业务层只需引用插件即可,同时也方便维护。

五、参考资料

上一篇下一篇

猜你喜欢

热点阅读