漫谈AOP开发初探之AspectJ的用法

Posted by JackPeng on August 30, 2017

一、为什么需要AOP技术

AOP 是一个很成熟的技术。 假如项目中有方法A、方法B、方法C……等多个方法, 如果项目需要为方法A、方法B、方法C……这批方法增加具有通用性质的横切处理。

下图可以形象的说明具有通用性质的横切处理的思想:

在以前传统的做法是

  1. 先定义一个Advice方法,该方法实现这个通用性质的横切处理。
  2. 打开方法A、方法B、方法C……的源代码修改,使得方法A、方法B、方法C……去调用Advice方法。

客户电话: 为每个方法都增加日志。

客户电话: 为每个方法前都增加权限控制。

客户电话: 为每个方法都加……

….

如果使用AOP,可以做到程序员无需修改方法A、方法B、方法C……,但又可以为方法A、方法B、方法C增加调用Advice方法。

面向切面编程(AOP)是作为面向对象编程(OOP)的补充:

AOP框架具有如下两个特征:

  1. 各步骤之间的良好隔离性。
  2. 源代码无关性。

二、AOP的功能

保证程序员不修改方法A、方法B、方法C……的前提下,可以为方法A、方法B、方法C……增加通用处理。

AOP的本质:依然要去【修改】方法A、方法B、方法C……

—— 只是这个修改由AOP框架完成,程序员不需要改。

AOP要求去修改,到底怎么去修改方法A、方法B、方法……

AOP的实现方式有两种

  1. AOP框架在编译阶段,就对目标类进行修改,得到的class文件已经是被修改过的。生成静态的AOP代理类(生成*.class文件已经被改掉了,需要使用特定的编译器)。以AspectJ为代表 —— 静态AOP框架。
  2. AOP框架在运行阶段,动态生成AOP代理(在内存中动态地生成AOP代理类),以实现对目标对象的增强。它不需要特殊的编译器。以Spring AOP为代表。—— 动态AOP框架。

上面两种,哪种性能更好?很明显静态的AOP框架更好。 下面我们进入AspectJ的学习.

三. AspectJ简介

AOP虽然是方法论,但就好像OOP中的Java一样,一些先行者也开发了一套语言来支持AOP。目前用得比较火的就是AspectJ了,它是一种几乎和Java完全一样的语言,而且完全兼容Java(AspectJ应该就是一种扩展Java,但它不是像Groovy[1]那样的拓展。)。当然,除了使用AspectJ特殊的语言外,AspectJ还支持原生的Java,只要加上对应的AspectJ注解就好。所以,使用AspectJ有两种方法:

  1. 完全使用AspectJ的语言。这语言一点也不难,和Java几乎一样,也能在AspectJ中调用Java的任何类库。AspectJ只是多了一些关键词罢了。
  2. 使用纯Java语言开发,然后使用AspectJ注解,简称@AspectJ。

3.1 Aspect (切面)

实现了cross-cutting功能,是针对切面的模块。最常见的是logging模块、方法执行耗时模块,这样,程序按功能被分为好几层,如果按传统的继承的话,商业模型继承日志模块的话需要插入修改的地方太多,而通过创建一个切面就可以使用AOP来实现相同的功能了,我们可以针对不同的需求做出不同的切面。

3.2 jointpoint(连接点)

Join Points(以后简称JPoints)是AspectJ中最关键的一个概念。什么是JPoints呢?JPoints就是程序运行时的一些执行点。那么,一个程序中,哪些执行点是JPoints呢?比如:

  • 一个函数的调用可以是一个JPoint。比如Log.e()这个函数。e的执行可以是一个JPoint,而调用e的函数也可以认为是一个JPoint。
  • 设置一个变量,或者读取一个变量,也可以是一个JPoint。比如Demo类中有一个debug的boolean变量。设置它的地方或者读取它的地方都可以看做是JPoints。
  • for循环可以看做是JPoint。

理论上说,一个程序中很多地方都可以被看做是JPoint,但是AspectJ中,只有如表1所示的几种执行点被认为是JPoints:

3.3 Pointcuts

怎么从一堆一堆的JPoints中选择自己想要的JPoints呢?恩,这就是Pointcuts的功能。一句话,Pointcuts的目标是提供一种方法使得开发者能够选择自己感兴趣的JoinPoints

3.4 advice(处理逻辑)

advice是我们切面功能的实现,它是切点的真正执行的地方。比如像写日志到一个文件中,advice(包括:before、after、around等)在jointpoint处插入代码到应用程序中。我们来看一看原AspectJ程序和反编译过后的程序。看完下面的图我们就大概明白了AspectJ是如何达到监控源程序的信息了。

关键词

说明

 

示例

before()

before advice

表示在JPoint执行之前,需要干的事情

after()

after advice

表示JPoint自己执行完了后,需要干的事情。

after():returning(返回值类型)

after():throwing(异常类型)

returning和throwing后面都可以指定具体的类型,如果不指定的话则匹配的时候不限定类型

假设JPoint是一个函数调用的话,那么函数调用执行完有两种方式退出,一个是正常的return,另外一个是抛异常。

注意,after()默认包括returning和throwing两种情况

返回值类型 around()

before和around是指JPoint执行前或执行后备触发,而around就替代了原JPoint

around是替代了原JPoint,如果要执行原JPoint的话,需要调用proceed

3.5 实例说明

@Before("execution(* android.app.Activity.on**(..))")
    public void onActivityMethodBefore(JoinPoint joinPoint) throws Throwable {
}
  • @Before 这是一个advice
  • execution 这是一个Join Point
  • (* android.app.Activity.on**(..)” 这是一个正则表达式
    • 第一个*表示返回值(任意类型) - 方法的路径(通过正则匹配) - ()表示方法的参数,可以指定类型
  • onActivityMethodBefore 表示切入点的方法

本例中的pointcuts是匿名定义,无法复用,也可以按如下格式定义:

@Pointcut("execution(* android.app.Activity.on**(..))"
public void myPointCut(){};

@Before("myPointCut()")
    public void onActivityMethodBefore(JoinPoint joinPoint) throws Throwable {
}

四、实战AspectJ

4.1 java中使用Aspectj

AspectJ是一个基于Java语言的AOP框架,提供了强大的AOP功能,其他很多AOP框架都借鉴或采纳其中的一些思想。

  1. 下载和安装AspectJ

    下载地址:http://www.eclipse.org/aspectj/downloads.php

    • 运行、下载得到的安装JAR包。
      java -jar /Users/guangjie.peng/development/aspectj-1.8.10.jar
      

      接下来,会自动检测JAVA_HOME,安装目录需要手动选择,一路Next; - 添加~/development/aspectj/bin到环境变量,配置完毕后输入terminal输入ajc,验证是否配置成功,该命令位于bin目录下。 - 添加lib下的两个jar到classpath: mac上添加到classpath的方法是:把jar包复制到/Library/Java/Extensions目录下。

  2. 使用AspectJ

  • step 1 编写如下java源文件:
   package com.example.aspectj;

/**
 * Created by guangjie.peng on 17/8/30.
 */

public class AspectTest {
    public static void main (String[] args){
       System.out.println("execute main methid!!");
    }
}
   
  • step 2 编写aspect如下:
public aspect AuthAspectj {
        // execution(* com.example.aspectj.*.*(..)执行 任意返回值 该包下的任意类的任意方法形参不限
        before():execution(* com.example.aspectj.*.*(..)){
// 对原来方法进行修改、增强。
        System.out.println("----------模拟执行权限检查----------");
        }
}
   
  • step 3 编译:
cd com/example/aspectj/
 ajc -d *.java
 java com.example.aspectj.AspectTest

得到输出如下:

 ----------模拟执行权限检查----------
execute main method!!

4.2 android studio 配置AspectJ 环境

4.2.1 新建Android project,并创建一个Android module:aspectj,在aspectj的build.gradle中配置如下代码:

import org.aspectj.bridge.IMessage
 import org.aspectj.bridge.MessageHandler
 import org.aspectj.tools.ajc.Main
 apply plugin: 'com.android.library'

 buildscript {

     repositories {
         jcenter()
     }
     dependencies {
         classpath 'com.android.tools.build:gradle:2.3.3'
         classpath 'org.aspectj:aspectjtools:1.8.9'
         classpath 'org.aspectj:aspectjweaver:1.8.9'
     }
 }



 android {
     compileSdkVersion 26
     buildToolsVersion "25.0.3"
     lintOptions {
         abortOnError false
     }
 }

 dependencies {
     compile 'org.aspectj:aspectjrt:1.8.9'
 }

 android.libraryVariants.all { variant ->
 //    LibraryPlugin plugin = project.plugins.getPlugin(LibraryPlugin)
     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)]
         MessageHandler handler = new MessageHandler(true);
         new Main().run(args, handler)

         def log = project.logger
         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:
                 case IMessage.INFO:
                     log.info message.message, message.thrown
                     break;
                 case IMessage.DEBUG:
                     log.debug message.message, message.thrown
                     break;
             }
         }
     }
 }


网上配置方法会报一个错误 No such property: project for class: com.android.build.gradle.LibraryPlugin.

修复办法:不使用 LibraryPlugin 直接使用 project

4.2.2 修改住module的build.gradle

  • 依赖aspectj module:dependencies{ compile project("aspectl")};
  • 并添加如下代码:
 import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath 'org.aspectj:aspectjtools:1.8.9'
    }
}

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

4.2.3 在model(aspectj)(main/java/xxx)中新建class -> ActivityLifeAspect

代码如下:

@Aspect
public class ActivityLifeAspect {

    private static final String TAG = ActivityLifeAspect.class.getSimpleName();

    @Before("execution (* com.github.zdongcoding.aspectjdemo.MainActivity.onCreate(..))")
    public void adviceOnCreate(JoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Toast.makeText((Context) joinPoint.getTarget(), "aspectj"+signature.toShortString(), Toast.LENGTH_SHORT).show();
        Log.e(TAG, ""+joinPoint.getTarget());
    }

}


运行,即可看到代码生效了。

4.2.4 查看编译后源码

源码路径在 : app/build/intermediates/classes/xxx/MainActivity.class 你会看到多了 如下代码:

JoinPoint var4 = Factory.makeJP(ajc$tjp_0, this, this, savedInstanceState);
     ActivityLifeAspect.aspectOf().adviceOnCreate(var4);

以上是一个aspectj的入门,更多语法可以参考官网教程。

本文参考:http://www.cnblogs.com/lihuidu/p/5802662.html

http://www.jianshu.com/p/139c87e2a2b5