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還是監控日誌都可以使用該方式進行嘗試.

作爲老司機,這是彎道超車的必備祕籍,天下武功、唯快不破!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章