什么是AOP
我們把某個方面的功能(日志功能)與其他一批對象(需要打日志的對象)進行隔離,降低這個功能與那一批對象的耦合性
接下來是面向切面編程中的幾個概念乙各,從別處拷貝來的:
- 通知峰髓、增強處理(Advice):就是你想要的功能膏萧,也就是上面說的日志冒窍、耗時計算等懒叛。
- 連接點(JoinPoint):允許你通知(Advice)的地方烦租,那可就真多了延赌,基本每個方法的前、后(兩者都有也行)叉橱,或拋出異常是時都可以是連接點(spring只支持方法連接點)挫以。AspectJ還可以讓你在構造器或屬性注入時都行,不過一般情況下不會這么做窃祝,只要記住掐松,和方法有關的前前后后都是連接點。
- 切點(Pointcut):上面說的連接點的基礎上,來定義切入點大磺,你的一個類里抡句,有15個方法,那就有十幾個連接點了對吧量没,但是你并不想在所有方法附件都使用通知(使用叫織入玉转,下面再說),你只是想讓其中幾個殴蹄,在調用這幾個方法之前究抓、之后或者拋出異常時干點什么,那么就用切入點來定義這幾個方法袭灯,讓切點來篩選連接點刺下,選中那幾個你想要的方法。
- 切面(Aspect):切面是通知和切入點的結合』現(xiàn)在發(fā)現(xiàn)了吧橘茉,沒連接點什么事,連接點就是為了讓你好理解切點搞出來的姨丈,明白這個概念就行了畅卓。通知說明了干什么和什么時候干(什么時候通過before,after蟋恬,around等AOP注解就能知道)翁潘,而切入點說明了在哪干(指定到底是哪個方法),這就是一個完整的切面定義歼争。
- 織入(weaving) 把切面應用到目標對象來創(chuàng)建新的代理對象的過程拜马。
AOP框架
AspectJ
java是通過C語言編譯成class文件的(javac這個工具是C寫的)
AspectJ是用一個專門的編譯器來代替javac去生成class文件,這樣就可以在編譯時做一些事情了(跟注解處理器類似沐绒,不過貌似比它更深入俩莽、徹底)
- eclipse
下載AspectJ,不用乔遮,所以說了 - Android Studio
AspectJ官方關于在gradle中的使用
原理
就是在一個方法上加一個注解扮超,使其在編譯時生成代碼,可以在方法前和方法后(稱為連接點)加上業(yè)務代碼比如打日志之類的
代理
是發(fā)生在運行時
AspectJ
和ButterKnife
都是在編譯時的
使用
- 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;
}
}
}
}
- 自定義一個注解申眼,用來做標記瞒津,標記在方法上(AspectJ會在編譯時替代javac,在被這個注解標記的方法前后插入邏輯代碼)
/**
* Created by xuekai on 2017/12/8.
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)//這里寫成RUNTIME是為了在處理邏輯代碼的時候可以拿到注解的內容
public @interface BehaviorTrace {
String value();
}
- 在要被處理的方法上用2中的注解注釋括尸,該方法可以被一個切點關聯(lián)巷蚪,關聯(lián)之后可以理解為切點即該方法
@BehaviorTrace(value = "add方法")
public void add(View view) {
Log.i("MainActivity", "add-->執(zhí)行方法");
}
- 創(chuàng)建一個類,用來處理切點
/**
* Created by xuekai on 2017/12/8.
*/
@Aspect
public class BehaviorAspect {
/**
* 定義一個切點濒翻,用來關聯(lián)一個方法屁柏,可以理解這個切點就是要處理的方法
* execution里面描述的是這個切點關聯(lián)的方法簽名啦膜,用正則表達式匹配,下面這個匹配出的是 注解為@com.xk.aopdemo.aspectj.BehaviorTrace淌喻,返回值為*僧家,方法名為*(這里的返回值和方法名都可以指定),參數(shù)為隨意 的方法
*/
@Pointcut("execution(@com.xk.aopdemo.aspectj.BehaviorTrace * *(..))")
public void annoBehavior() {
}
/**
* 該方法對切點進行處理裸删,參數(shù)就是切點方法
* 該方法被Around注解八拱,表示可以在切點前后加業(yè)務邏輯
* Around里面的參數(shù)是annoBehavior,表示該方法處理的切點是如
* 上方法注解中聲明的切點
* 通過這個參數(shù)涯塔,就可以拿到方法的簽名肌稻,從而拿到各種數(shù)據
* ,調用 object = point.proceed();即可執(zhí)行切點方法
*/
@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í)行時
Object object = null;
object = point.proceed();
//方法執(zhí)行完成
Log.i("BehaviorAspect", "dealPoint-->后" + behaviorTrace.value());
return object;
}
}
關于編譯流程的猜想
代碼編譯時匕荸,不再用javac
了爹谭,而是用AspectJ
的編譯器來編譯,首先會找到@Aspect
注解的類榛搔,然后找到被@Around
注解的方法(還有其他類型的注解诺凡,現(xiàn)在先不考慮),開始處理切點方法践惑,具體處理哪些方法呢腹泌?找@Around
的參數(shù),通過匹配@Pointcut
的參數(shù)中的方法簽名尔觉,凡是遇到該簽名的方法真屯,編譯的時候都按照處理該切點方法的方式去處理
可以看看切點方法編譯后的class代碼