Android中的面向切面編程——AspectJ框架的簡(jiǎn)單使用

什么是AOP

我們把某個(gè)方面的功能(日志功能)與其他一批對(duì)象(需要打日志的對(duì)象)進(jìn)行隔離,降低這個(gè)功能與那一批對(duì)象的耦合性

接下來(lái)是面向切面編程中的幾個(gè)概念,從別處拷貝來(lái)的:

  • 通知、增強(qiáng)處理(Advice):就是你想要的功能,也就是上面說(shuō)的日志、耗時(shí)計(jì)算等。
  • 連接點(diǎn)(JoinPoint):允許你通知(Advice)的地方,那可就真多了,基本每個(gè)方法的前、后(兩者都有也行),或拋出異常是時(shí)都可以是連接點(diǎn)(spring只支持方法連接點(diǎn))。AspectJ還可以讓你在構(gòu)造器或?qū)傩宰⑷霑r(shí)都行,不過(guò)一般情況下不會(huì)這么做,只要記住,和方法有關(guān)的前前后后都是連接點(diǎn)。
  • 切點(diǎn)(Pointcut):上面說(shuō)的連接點(diǎn)的基礎(chǔ)上,來(lái)定義切入點(diǎn),你的一個(gè)類里,有15個(gè)方法,那就有十幾個(gè)連接點(diǎn)了對(duì)吧,但是你并不想在所有方法附件都使用通知(使用叫織入,下面再說(shuō)),你只是想讓其中幾個(gè),在調(diào)用這幾個(gè)方法之前、之后或者拋出異常時(shí)干點(diǎn)什么,那么就用切入點(diǎn)來(lái)定義這幾個(gè)方法,讓切點(diǎn)來(lái)篩選連接點(diǎn),選中那幾個(gè)你想要的方法。
  • 切面(Aspect):切面是通知和切入點(diǎn)的結(jié)合?,F(xiàn)在發(fā)現(xiàn)了吧,沒(méi)連接點(diǎn)什么事,連接點(diǎn)就是為了讓你好理解切點(diǎn)搞出來(lái)的,明白這個(gè)概念就行了。通知說(shuō)明了干什么和什么時(shí)候干(什么時(shí)候通過(guò)before,after,around等AOP注解就能知道),而切入點(diǎn)說(shuō)明了在哪干(指定到底是哪個(gè)方法),這就是一個(gè)完整的切面定義。
  • 織入(weaving) 把切面應(yīng)用到目標(biāo)對(duì)象來(lái)創(chuàng)建新的代理對(duì)象的過(guò)程。

AOP框架

AspectJ

java是通過(guò)C語(yǔ)言編譯成class文件的(javac這個(gè)工具是C寫的)
AspectJ是用一個(gè)專門的編譯器來(lái)代替javac去生成class文件,這樣就可以在編譯時(shí)做一些事情了(跟注解處理器類似,不過(guò)貌似比它更深入、徹底)

原理

就是在一個(gè)方法上加一個(gè)注解,使其在編譯時(shí)生成代碼,可以在方法前和方法后(稱為連接點(diǎn))加上業(yè)務(wù)代碼比如打日志之類的
代理是發(fā)生在運(yùn)行時(shí)
AspectJButterKnife都是在編譯時(shí)的

使用

  1. module下的build.gradle中作如下配置
import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main
apply plugin: 'com.android.application'
android {
    ...
}
dependencies {
    ...
    compile 'org.aspectj:aspectjrt:1.8.1'
}
buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath 'org.aspectj:aspectjtools:1.8.9'
        classpath 'org.aspectj:aspectjweaver:1.8.9'
    }
}
repositories {
    mavenCentral()
}
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.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;
            }
        }
    }
}
  1. 自定義一個(gè)注解,用來(lái)做標(biāo)記,標(biāo)記在方法上(AspectJ會(huì)在編譯時(shí)替代javac,在被這個(gè)注解標(biāo)記的方法前后插入邏輯代碼)
/**
 * Created by xuekai on 2017/12/8.
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)//這里寫成RUNTIME是為了在處理邏輯代碼的時(shí)候可以拿到注解的內(nèi)容
public @interface BehaviorTrace {
    String value();
}
  1. 在要被處理的方法上用2中的注解注釋,該方法可以被一個(gè)切點(diǎn)關(guān)聯(lián),關(guān)聯(lián)之后可以理解為切點(diǎn)即該方法
    @BehaviorTrace(value = "add方法")
    public void add(View view) {
        Log.i("MainActivity", "add-->執(zhí)行方法");
    }
  1. 創(chuàng)建一個(gè)類,用來(lái)處理切點(diǎn)
/**
 * Created by xuekai on 2017/12/8.
 */
@Aspect
public class BehaviorAspect {

    /**
     * 定義一個(gè)切點(diǎn),用來(lái)關(guān)聯(lián)一個(gè)方法,可以理解這個(gè)切點(diǎn)就是要處理的方法
     * execution里面描述的是這個(gè)切點(diǎn)關(guān)聯(lián)的方法簽名,用正則表達(dá)式匹配,下面這個(gè)匹配出的是 注解為@com.xk.aopdemo.aspectj.BehaviorTrace,返回值為*,方法名為*(這里的返回值和方法名都可以指定),參數(shù)為隨意 的方法
     */
    @Pointcut("execution(@com.xk.aopdemo.aspectj.BehaviorTrace  * *(..))")
    public void annoBehavior() {
    }

    /**
     * 該方法對(duì)切點(diǎn)進(jìn)行處理,參數(shù)就是切點(diǎn)方法
     * 該方法被Around注解,表示可以在切點(diǎn)前后加業(yè)務(wù)邏輯
     * Around里面的參數(shù)是annoBehavior,表示該方法處理的切點(diǎn)是如      
     * 上方法注解中聲明的切點(diǎn)
     * 通過(guò)這個(gè)參數(shù),就可以拿到方法的簽名,從而拿到各種數(shù)據(jù)
     * ,調(diào)用 object = point.proceed();即可執(zhí)行切點(diǎn)方法
     */
    @Around("annoBehavior()")
    public Object dealPoint(ProceedingJoinPoint point) throws Throwable {
        //方法執(zhí)行前
        MethodSignature methodSignature = (MethodSignature) point.getSignature();
        BehaviorTrace behaviorTrace = methodSignature.getMethod().getAnnotation(BehaviorTrace.class);
        Log.i("BehaviorAspect", "dealPoint-->前" + behaviorTrace.value());
        long beagin = System.currentTimeMillis();
        //方法執(zhí)行時(shí)
        Object object = null;
        object = point.proceed();
        //方法執(zhí)行完成
        Log.i("BehaviorAspect", "dealPoint-->后" + behaviorTrace.value());

        return object;
    }
}

關(guān)于編譯流程的猜想

代碼編譯時(shí),不再用javac了,而是用AspectJ的編譯器來(lái)編譯,首先會(huì)找到@Aspect注解的類,然后找到被@Around注解的方法(還有其他類型的注解,現(xiàn)在先不考慮),開(kāi)始處理切點(diǎn)方法,具體處理哪些方法呢?找@Around的參數(shù),通過(guò)匹配@Pointcut的參數(shù)中的方法簽名,凡是遇到該簽名的方法,編譯的時(shí)候都按照處理該切點(diǎn)方法的方式去處理
可以看看切點(diǎn)方法編譯后的class代碼

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容