什么是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ò)貌似比它更深入、徹底)
- eclipse
下載AspectJ,不用,所以說(shuō)了 - Android Studio
AspectJ官方關(guān)于在gradle中的使用
原理
就是在一個(gè)方法上加一個(gè)注解,使其在編譯時(shí)生成代碼,可以在方法前和方法后(稱為連接點(diǎn))加上業(yè)務(wù)代碼比如打日志之類的
代理是發(fā)生在運(yùn)行時(shí)
AspectJ和ButterKnife都是在編譯時(shí)的
使用
- 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;
}
}
}
}
- 自定義一個(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();
}
- 在要被處理的方法上用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í)行方法");
}
- 創(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代碼