Android AOP三剑客之AspectJ

前言

本章节目的不是详细的介绍AspectJ的细节,而是最近项目用到了AspectJ,通过一个简单例子来看下定义切片以及使用切片的流程是怎样的。

AspectJ

  • AspectJ 是使用最为广泛的 AOP 实现方案,适用于 Java 平台,官网地址:http://www.eclipse.org/aspectj/ 。AspectJ 是在静态织入代码,即在编译期注入代码的。

  • AspectJ 提供了一套全新的语法实现,完全兼容 Java(跟 Java 之间的区别,只是多了一些关键词而已)。同时,还提供了纯 Java 语言的实现,通过注解的方式,完成代码编织的功能。因此我们在使用 AspectJ 的时候有以下两种方式:

    • 使用 AspectJ 的语言进行开发

    • 通过 AspectJ 提供的注解在 Java 语言上开发

  • 因为最终的目的其实都是需要在字节码文件中织入我们自己定义的切面代码,不管使用哪种方式接入 AspectJ,都需要使用 AspectJ 提供的代码编译工具 ajc 进行编译。

  • 在 Android Studio 上一般使用注解的方式使用 AspectJ,因为 Android Studio 没有 AspectJ 插件,无法识别 AspectJ 的语法(不过在 Intellij IDEA 收费版上可以使用 AspectJ 插件),所以后面的语法说明和示例都是以注解的实现方式。

常用术语

在了解AspectJ的具体使用之前,先了解一下其中的一些基本的术语概念,这有利于我们掌握AspectJ的使用以及AOP的编程思想。

JoinPoints

JoinPoints(连接点),程序中可能作为代码注入目标的特定的点。在AspectJ中可以作为JoinPoints的地方包括:


PointCuts

PointCuts(切入点),其实就是代码注入的位置。与前面的JoinPoints不同的地方在于,其实PointCuts是有条件限定的JoinPoints。比如说,在一个Java源文件中,会有很多的JoinPoints,但是我们只希望对其中带有@debug注解的地方才注入代码。所以,PointCuts是通过语法标准给JoinPoints添加了筛选条件限定。

Advice

Advice(通知),其实就是注入到class文件中的代码片。典型的 Advice 类型有 before、after 和 around,分别表示在目标方法执行之前、执行后和完全替代目标方法执行的代码。

Aspect

Aspect(切面),Pointcut 和 Advice 的组合看做切面。
Weaving
注入代码(advices)到目标位置(joint points)的过程

接下来通过项目看一下实践过程

传送门:android-aop-samples

在annotation里定义注解

@Retention(RetentionPolicy.CLASS)
@Target(ElementType.METHOD)
 public @interface CheckLogin {
 }

在android studio的android工程中使用AspectJ的时候,我们需要在项目的build.gradle的文件中添加一些配置:

dependencies {
    classpath 'org.aspectj:aspectjtools:1.8.9'
    ...
}

在新建module里定义AspectjPlugin,也可以直接写到gradle里面,固定写法没啥说的

public class AspectjPlugin implements Plugin<Project> {


void apply(Project project) {
    project.dependencies {
        compile 'org.aspectj:aspectjrt:1.8.9'
    }
    final def log = project.logger
    log.error "========================";
    log.error "Aspectj切片开始编织Class!";
    log.error "========================";
    project.android.applicationVariants.all { variant ->
        def 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;
                }
            }
        }
    }
}
}

在app的build.gradle里面

  import com.app.plugin.AspectjPlugin
  apply plugin: AspectjPlugin

定义切片

@Aspect
public class CheckLoginAspect {

@Pointcut("execution(@com.app.annotation.aspect.CheckLogin * *(..))")//方法切入点
public void methodAnnotated() {
}

@Around("methodAnnotated()")//在连接点进行方法替换
public void aroundJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable {


    if (!SharedPreferenceUtil.isLogin()) {
        Snackbar.make(AopApplication.getAppContext().getCurActivity().getWindow().getDecorView(), "请先登录!", Snackbar.LENGTH_LONG)
                .setAction("登录", new View.OnClickListener() {
                    @Override
                    public void onClick(View view) {
                        SharedPreferenceUtil.setLogin(AopApplication.getAppContext(), true);
                        Toast.makeText(AopApplication.getAppContext(), "登录成功", Toast.LENGTH_SHORT).show();
                    }
                }).show();

        return;
    }

    joinPoint.proceed();//执行原方法
}
}

在MainActivity里面使用注解@CheckLogin,看下build/intermediates/classes编译出来的class里面的插入代码

@CheckLogin
public void doMarkDown()
{
JoinPoint localJoinPoint = Factory.makeJP(ajc$tjp_0, this, this);Object[] arrayOfObject = new 
Object[2];arrayOfObject[0] = this;arrayOfObject[1] = 
localJoinPoint;CheckLoginAspect.aspectOf().aroundJoinPoint(new 
MainActivity.AjcClosure1(arrayOfObject).linkClosureAndJoinPoint(69648));
 }

static final void doMarkDown_aroundBody0(MainActivity ajc$this, JoinPoint paramJoinPoint)
{
Toast.makeText(AopApplication.getAppContext(), , 1).show();
}

使用总结

1.定义注解
2.添加入口plugin或者直接写在gradle里
3.定义切片,设置@Pointcut使用execution来设置方法的切入点为com.app.annotation.aspect包下的CheckLogin
4.编写切片处理逻辑在Advice里,Advice就是我们插入的代码可以以何种方式插入,有Before 还有 After、Around
5.在项目里使用切片,达到在指定位置插入代码的目的,可以在具体项目里面同一种场景使用该注解达到处理切面的问题,大大减少了代码的书写,更加是AOP的具体体现,对OOP的一种弥补

最后

本章节只是介绍了少部分的AspectJ的使用,还是那句老话,AspectJ本身并没有技术难点,难的是怎么设计出好用的切面,无论是log还是监控日志都可以使用该方式进行尝试.

作为老司机,这是弯道超车的必备秘籍,天下武功、唯快不破!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,366评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,521评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,689评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,925评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,942评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,727评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,447评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,349评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,820评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,990评论 3 337
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,127评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,812评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,471评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,017评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,142评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,388评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,066评论 2 355

推荐阅读更多精彩内容

  • AOP:面向切面编程(Aspect-Oriented Programming)。如果说,OOP如果是把问题划分到单...
    North_2016阅读 46,763评论 38 269
  • 用两张图告诉你,为什么你的 App 会卡顿? - Android - 掘金 Cover 有什么料? 从这篇文章中你...
    hw1212阅读 12,732评论 2 59
  • 这几天,颇为不静。 守住内心。
    敏非敏阅读 197评论 3 12
  • 朋友于先生是专做大型机械的,他手下有两台挖土机,一年少则挣几十万,多则挣个百来万元,在镇上也小有名气,认识的人,都...
    小镇名家阅读 387评论 4 5
  • 活动一 活动需要:买几盒好看的明信片 活动内容:在明信片上写下对爱情的见解、疑问、祝福、告白。由活动负责人随机互换...
    碎雨等谷雨阅读 136评论 0 0