AOP面向切面编程,AspectJ在Android中应用二三事(上)

AOP面向切面编程,AspectJ在Android中应用二三事(上)_第1张图片

1.简述

1.1 AOP的概念

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

1.2 AOP术语
  1. 通知、增强处理(Advice):就是你想要的功能,也就是上面说的日志、耗时计算等。
  2. 连接点(JoinPoint):允许你通知(Advice)的地方,那可就真多了,基本每个方法的前、后(两者都有也行),或抛出异常是时都可以是连接点(spring只支持方法连接点)。AspectJ还可以让你在构造器或属性注入时都行,不过一般情况下不会这么做,只要记住,和方法有关的前前后后都是连接点。
  3. 切入点(Pointcut):上面说的连接点的基础上,来定义切入点,你的一个类里,有15个方法,那就有十几个连接点了对吧,但是你并不想在所有方法附件都使用通知(使用叫织入,下面再说),你只是想让其中几个,在调用这几个方法之前、之后或者抛出异常时干点什么,那么就用切入点来定义这几个方法,让切点来筛选连接点,选中那几个你想要的方法。
  4. 切面(Aspect):切面是通知和切入点的结合。现在发现了吧,没连接点什么事,连接点就是为了让你好理解切点搞出来的,明白这个概念就行了。通知说明了干什么和什么时候干(什么时候通过before,after,around等AOP注解就能知道),而切入点说明了在哪干(指定到底是哪个方法),这就是一个完整的切面定义。
  5. 织入(weaving) 把切面应用到目标对象来创建新的代理对象的过程。
1.3 AOP注解与使用
  • @Aspect: 声明切面,标记类
  • @Pointcut(切点表达式):定义切点,标记方法
  • @Befor(切点表达式):前置通知,切点之前执行
  • @After(切点表达式):后置通知,切点之后执行
  • @Around(切点表达式):环绕通知,切点前后执行
  • @AfterReturning(切点表达式):返回通知,切点方法返回结果之后执行
  • @AfterThrowing(切点表达式):异常通知,切点抛出异常时执行

@Pointcut @Befor @Around @After @AfterReturning @AfterThrowing 需要在切面类使用,即:@Aspect类中

1.4 AOP实现方式
  1. AspectJ: 一个 JavaTM 语言的面向切面编程的无缝扩展(适用Android)。
  2. Javassist for Android: 用于字节码操作的知名 java 类库 Javassist 的 Android 平台移植版。
  3. DexMaker: Dalvik 虚拟机上,在编译期或者运行时生成代码的 Java API。
  4. ASMDEX: 一个类似 ASM 的字节码操作库,运行在Android平台,操作Dex字节码。

本文主要讲解的就是aspectJ在Android中的使用。

1.4 场景

AOP这种编程思想有什么用呢?一般来说,主要用于不想侵入原有代码的场景中,例如:业务代码中插入一些日志埋点、性能监控、动态权限控制甚至代码调试等,会导致业务代码逻辑不清晰,业务不分离等问题。

2. AspectJ使用

2.1 AspectJ如何集成
dependencies {   
  classpath 'com.android.tools.build:gradle:3.4.2'
//引用aspcetJ的依赖
  classpath 'org.aspectj:aspectjtools:1.8.9'
  classpath 'org.aspectj:aspectjweaver:1.8.9'
    }

然后在项目使用的具体module的build.gradle中添加相应的依赖:

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'androidx.appcompat:appcompat:1.0.2'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    //引用aspcetJ的jar
    implementation 'org.aspectj:aspectjrt:1.8.9'
}

然后在module的build.gradle根路径添加解析代码块:

import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main
final def log = project.logger

android.applicationVariants.all{ variant ->

    JavaCompile javaCompile = variant.javaCompiler
    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
            }
        }
    }
}

重新rebuild,aspectJ就集成到我们的项目当中了。

2.2配置相关的切面类

代码如下:


@Aspect
public class LogTraceAspect {

    private static final String TAG = "LOG";

    @Before("execution(* android.app.Activity.on*(..))")
    public void onActivityMethodBefore(JoinPoint joinPoint) throws Throwable {
        String key = joinPoint.getSignature().toString();
        Log.e(TAG, "onActivityMethodBefore:" + key);
    }
}

以上就是一个简单的切面类,具体的注解可以看1.3的说明,这个很简单,难点是切点表达式。搞懂切点表达式,AOP的东西就很简单了。

2.3 切点表达式

切点表达式的组成如下:

execution(<修饰符模式>? <返回类型模式> <方法名模式>(<参数模式>) <异常模式>?)

2.2中在切面类有一个表达式

“execution(* android.app.Activity.on*(…))”
除了返回类型模式、方法名模式和参数模式外,其他的都是可选的。

以2.2的表达式举例说明:execution 一般指定方法的执行,因为返回类型没有限制,这里就用 * 代替,Android.app.Activity.on * 代表函数的全路径,意思是所有包名Android.app.activity下以on开头的所有方法。后面的(…)表示方法的参数是任何值。

JPoint分类和方法说明

JPoint 说明 Pointcut语法说明
method execution 一般指定方法的执行 execution(MethodSignnature)
method call 函数方法被调用 call(MethodSignnature)
constructor call 构造函数被调用 call(ConstructorSignature)
constructor exxcution 构造函数内部执行 execution(ConstructorSignature)
field get 读变量 get(FieldSignature)
field set 写变量 set(FieldSignature)
handler 异常处理 handler(TypeSignature)注意:只能和@Before()配合使用,不支持@After和@Around等

举例说明

    @Before("call(* android.app.Activity.on*(..))")
    public void callMethod(JoinPoint joinPoint) throws Throwable {
        String key = joinPoint.getSignature().toString();
        Log.e(TAG, "callMethodBefore" + key);
    }

这段代码的意思是匹配所有的activity,在activity的on方法调用之前打印日志。

    @Before("execution(* android.app.Activity.on*(..))")
    public void onActivityMethodBefore(JoinPoint joinPoint) throws Throwable {
        String key = joinPoint.getSignature().toString();
        Log.e(TAG, "onActivityMethodBefore:" + key);
    }

这段代码的意思是匹配所有的activity,在activity的on方法中方法体执行之前打印日志。

execution 和call的区别在于call执行位置是在方法之前,execution执行的位置是方法内部第一行。

get和set方法说明

首先我们先定义一个对象类

public class TestUser {

    private int  age;
    private String name;

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

执行方法:

 /**
     * 获取到类的构造方法,并且给方法赋值
     * @param joinPoint
     * @return
     * @throws Throwable
     */
    @Around("get(com.android.aspectj.TextUser.age)")
    public String onGetAge(ProceedingJoinPoint joinPoint) throws Throwable{
        // 执行原代码
        Object obj = joinPoint.proceed();
        String age = obj.toString();
        Log.e(TAG, "age: " + age);
        return "18";
    }

获取到我们设置给testUser类的设置年龄的方法,并且将年龄设置成18岁返回给setAge()方法。get方法也是一样。

2.4 注解使用

有时候我们并不知道具体的第三方名称,或者很多的方法都需要我们来记录,这时候注解就可以很好的解决这个需求。
先来自定义一段注解:


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

然后在Aspect文件中引用监听的注解:

 @Before("execution(@com.android.aspectj.log.LogTraceAspectInterface.Debug * * (..))")
    public void onActivityDebug(JoinPoint joinPoint) throws Throwable {
        String key = joinPoint.getSignature().toString();
        Log.e(TAG, "Debug:" + key);
    }

要注意路径一定是我们自定义注解的全路径
最后在想要记录的地方直接引入注解即可

 
    @LogTraceAspectInterface.Debug
    public void click(){
        Toast.makeText(getApplicationContext(),"哈哈",Toast.LENGTH_SHORT).show();
    }

好啦,这期关于Aspect的简单介绍就到这里了,代码我已经上传到github
欢迎加我V 18201726027或者私聊交流探讨,一起学习,一起进步成长!
下期预告:AspectJ开发面向切片编程分module的实际运用。

你可能感兴趣的:(android学习小)