AOP编程实战-AspectJ

简介:Aspect Oriented Programming,面向切面编程。 经过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。 AOP是OOP的延续,利用AOP能够对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度下降,提升程序的可重用性,同时提升了开发的效率。

可能看简介有点抽象,接下来咱们从一个开发中常见的问题入手,应该会更具体一些。咱们知道在app开发中常常有判断当前是否登陆的状况,好比在抖音首页中就算咱们没有登陆仍是能够看视频,可是若是想要点赞评论或者切换下面tab时就会须要咱们登陆了。按照最简单的思路,咱们固然能够在点击这全部的按钮的时候判断一下当前的登陆状态,若是未登陆的话就跳转到登陆页面。可是,这样作的话是否太过复杂?该页面中有上十个按钮都须要判断登陆状态,重复的代码太多,这种状况就须要优化了。那么应该如何进行优化呢?咱们能够参考一下ARouter的实现,在ARouter中有一个拦截器,咱们经过实现拦截器能够拦截下来跳转请求,而且在其中进行判断登陆状态。重点是拦截器只须要写一次代码,节省重复劳动。
java


那么拦截器是什么原理呢?这个咱们经过ARouter源码能够看到,ARouter中的拦截器是经过@Interceptor注解进行标识,而后在ARouter初始化时会调用init方法,其中就是执行自定义拦截器的process方法。这里的将Interceptor统一处理拦截的方式就是AOP操做,提炼出了这些操做的共性,而且进行统一处理,共性就是须要拦截和判断。android



除了APT来实现AOP还有其余的方式,具体的方式以及优劣见下图。编程

咱们知道APT是在编译器经过注解来采集信息,而后经过注解处理器来生成代码,生成的代码和普通的java代码同样会被打包成class使用。AspectJ是第三方提供的框架,是在编译之后,按照class文件的规则经过文件流去修改class文件。AspectJ底层是使用了ASM,和ASM原理相似。Qzone超级补丁、AS中的Instant run功能都是使用的ASM直接操做字节码。以上三种方式都是属于预编译方式来实现AOP,而不是运行期动态代理的方式,因此不影响效率。bash

接下来笔者会以工做中常常碰到的其余2种场景举例,演示一下AOP的简单实现。那么应该用以上三种方式的哪种呢,APT的方式时经过生成一个class类文件,而后再运行的时候去调用这个新文件中的代码来实现咱们想要的功能,由于生成之后还须要调用因此侵入性很强,适合那种写模板代码的状况,但如今的需求是修改已有代码,那么ASM?ASM是最轻量的、性能最好、最强大的,可是使用起来太复杂,因此优先使用AspectJ来实现。app


在此以前,先简单介绍一下AspectJ的简单使用方式框架

步骤一 导包异步

前面说过AspectJ是一个第三方库,因此须要导入相关的依赖到工程中
性能

1.在buildscript中加入
gradle

buildscript {
优化

    repositories {

       google()

      jcenter()

   } dependencies {

      classpath 'org.aspectj:aspectjtools:1.9.2'

   }

}

2.在模块的的dependencies中加入

dependencies {

     implementation 'org.aspectj:aspectjrt:1.9.2'

}

步骤二  配置Aspect执行脚本

须要将Aspect的执行脚本复制到gradle中

//在构建工程时,执行编织
project.android.applicationVariants.all { variant ->    
JavaCompile javaCompile = variant.javaCompile    
//在编译后 增长行为    
javaCompile.doLast {        
println "执行AspectJ编译器......"        
String[] args = [                
"-1.7",               
 //aspectJ 处理的源文件                
"-inpath", javaCompile.destinationDir.toString(),               
 //输出目录,aspectJ处理完成后的输出目录                
"-d", javaCompile.destinationDir.toString(),                
//aspectJ 编译器的classpath aspectjtools                
"-aspectpath", javaCompile.classpath.asPath,                
//java的类查找路径                
"-classpath", javaCompile.classpath.asPath,               
 //覆盖引导类的位置  android中使用android.jar 而不是jdk                
"-bootclasspath", project.android.bootClasspath.join(File.pathSeparator)]       
 new Main().runMain(args, false)    }}复制代码


案例1.进行线程切换时,每次都要调用一大堆的Handle/RxJava代码API

步骤一  定义2个注解分别表明子线程和主线程

其中Async表明子线程标识,Main表明主线程标识,定义注解的缘由是为了和Aspect配合使用,来标识须要使用切面的方法,毕竟不是全部的方法都须要使用线程切换的切面的。

步骤二  定义好子线程和主线程的切面

须要告诉系统,应该怎么处理注解标识好的方法,这里就要使用到Aspect库了。咱们定义一个类,用@Aspect进行标记,而后定义一个方法,以异步线程为例,在定义好的方法上面用@Around标注,而后标注中按照execution(@注解类名 返回值 方法名和参数)的方式进行标注,其中*为通配符,以下图中我用void *(..)表明处理无返回值的任意方法、任意参数的方法。也就是说只要是用Async注解进行了标注了而且没有返回值的方法就会被拦截下来再也不执行,被咱们定义的doAsyncMethod方法代替。在拦截到方法后,咱们使用rxjava中的线程切换将当前线程切换到子线程。须要注意的是,这个joinPoint参数就是核心了,它表明着原方法中全部的执行步骤,以参数的形式传到了切面,可供咱们在任何想要的地方进行调用。本例中,在线程切换完成后,咱们调用了原方法,因此达到了切换线程的目的。固然,主线程标记以同样。

步骤三  用自定义的注解去标识须要使用切面的类

必须让系统知道哪些方法须要切换线程,因此咱们须要用注解进行标识,这也是咱们定义注解的缘由。好比咱们在io流读写文件的时候用子线程,读取完成之后须要更新UI必须再主线程,因此咱们将读写文件的方法用Async来标记,而后将UI操做的方法使用Main来操做。操做完成之后,AspectJ就会拦截这些方法,根据使用的哪一个注解来执行相应的切面。



案例2.须要输入一些日志,每次都须要组装不一样字符串

案例2和案例1很是相似,此次咱们新增一个参数表明日志的类型吧,在定义注解的时候新增了一个value字段,用来搜集打日志的类型。而后须要注意的是,若是将注解中的参数传递过来,须要获取到注解类,调用注解类的方法。具体的切面代码就不贴了,和案例1相似,能够参照案例1,而后下面贴出获取到注解传的参数的方式。


Logger logger = method.getAnnotation(Logger.class);    //获取到注解

String loggerType = looger.value();    //获取到日志类型参数复制代码


总结:本文介绍了跳转登陆、线程切换、打日志等几种状况下AOP的应用。可是实际上AOP能用到的场景远远不止这些,好比参数校验和判空、动态权限处理、埋点、性能统计等不少其余地方均可以使用AOP进行优化。除此以外,本文只是介绍了编译时修改的方式进行AOP编程,还有运行期动态代理的方式没有介绍,等之后有空再更新。

相关文章
相关标签/搜索