一、簡述
1.AOP概念
AOP為Aspect Oriented Programming的縮寫朗兵,意為:面向切面編程污淋,通過預(yù)編譯方式和運行期動態(tài)代理實現(xiàn)程序功能的統(tǒng)一維護的一種技術(shù)。一句話概括:在運行時余掖,動態(tài)地將代碼切入到類的指定方法、指定位置上的編程思想就是面向切面的編程礁鲁。
2.運用場景
項目開發(fā)過程中盐欺,可能會有這樣的需求,需要我們在方法執(zhí)行完成后仅醇,記錄日志(后臺開發(fā)中比較常見~)冗美,或是計算這個方法的執(zhí)行時間,在不使用AOP的情況下析二,我們可以在方法最后調(diào)用另一個專門記錄日志的方法粉洼,或是在方法體的首尾分別獲取時間,然后通過計算時間差來計算整個方法執(zhí)行所消耗的時間叶摄,這樣也可以完成需求属韧。那如果不只一個方法要這么玩怎么辦?每個方法都寫上一段相同的代碼嗎蛤吓?后期處理邏輯變了要怎么辦宵喂?其實這種問題我們完全可以用AOP來解決。
3.AOP的實現(xiàn)方式
AOP僅僅只是個概念会傲,實現(xiàn)它的方式(工具和庫)有以下幾種:
- AspectJ: 一個 JavaTM 語言的面向切面編程的無縫擴展(適用Android)锅棕。
- Javassist for Android: 用于字節(jié)碼操作的知名 java 類庫 Javassist 的 Android 平臺移植版。
- DexMaker: Dalvik 虛擬機上淌山,在編譯期或者運行時生成代碼的 Java API裸燎。
- ASMDEX: 一個類似 ASM 的字節(jié)碼操作庫,運行在Android平臺泼疑,操作Dex字節(jié)碼德绿。
下面就來看看AspectJ方式的AOP如何在Android開發(fā)中進行使用的。
二王浴、AspectJ導入
項目gradle添加
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.4.0'
classpath 'org.aspectj:aspectjtools:1.9.4'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
主model添加依賴
dependencies {
...
implementation 'org.aspectj:aspectjrt:1.9.4'
}
添加gradle任務(wù),直接粘貼到build.gradle文件的末尾即可脆炎,不要嵌套在別的指令中。
import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main
final def log = project.logger
final def variants = project.android.applicationVariants
//在構(gòu)建工程時氓辣,執(zhí)行編輯
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.9",
"-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基礎(chǔ)知識
四、AspectJ實現(xiàn)AOP
1.創(chuàng)建自定義注解
用注解來標記切點钞啸,一般會使用自定義注解几蜻,方便我們拓展喇潘。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnoTrace {
String value();
int type();
}
- @Target(ElementType.METHOD):表示該注解只能注解在方法上。如果想類和方法都可以用梭稚,那可以這么寫:@Target({ElementType.METHOD,ElementType.TYPE})颖低,依此類推。
- @Retention(RetentionPolicy.RUNTIME):表示該注解在程序運行時是可見的(還有SOURCE弧烤、CLASS分別指定注解對于那個級別是可見的忱屑,一般都是用RUNTIME)。
其中的value和type是自己拓展的屬性暇昂,方便存儲一些額外的信息莺戒。
2.使用自定義注解標記切點
這個自定義注解只能注解在方法上(構(gòu)造方法除外,構(gòu)造方法也叫構(gòu)造器急波,需要使用ElementType.CONSTRUCTOR)从铲,像平常使用其它注解一樣使用它即可:
@TestAnnoTrace(value = "test", type = 1)
public void test(View view) {
System.out.println("Hello");
}
3.創(chuàng)建切面類
切點表達式的組成如下:
execution(<@注解類型模式>? <修飾符模式>? <返回類型模式> <方法名模式>(<參數(shù)模式>) <異常模式>?)
要織入一段代碼到目標類方法的前前后后,必須要有一個切面類澄暮,下面就是切面類的代碼:
@Aspect
public class TestAnnoAspect {
//切點表達式使用自定義的注解名段,一定是@+注解全路徑
@Pointcut("execution(@com.hxl.androidaopdemo.TestAnnoTrace * *(..))")
public void pointcut() {}
@Before("pointcut()")
public void before(JoinPoint point) {
System.out.println("@Before");
}
@Around("pointcut()")
public void around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("@Around");
joinPoint.proceed();// 目標方法執(zhí)行完畢
}
@After("pointcut()")
public void after(JoinPoint point) {
System.out.println("@After");
}
@AfterReturning("pointcut()")
public void afterReturning(JoinPoint point, Object returnValue) {
System.out.println("@AfterReturning");
}
@AfterThrowing(value = "pointcut()", throwing = "ex")
public void afterThrowing(Throwable ex) {
System.out.println("@afterThrowing");
System.out.println("ex = " + ex.getMessage());
}
}
打印結(jié)果:
@Before
@Around
Hello
@After
@AfterReturning
少了一個@AfterThrowing通知。這個通知只有在切點拋出異常時才會執(zhí)行
4.JoinPoint的作用
發(fā)現(xiàn)沒有泣懊,上面所有的通知都會至少攜帶一個JointPoint參數(shù)伸辟,這個參數(shù)包含了切點的所有信息,下面就結(jié)合按鈕的點擊事件方法test()來解釋joinPoint能獲取到的方法信息有哪些:
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
String name = signature.getName(); // 方法名:test
Method method = signature.getMethod(); // 方法:public void com.hxl.androidaopdemo.MainActivity.test(android.view.View)
Class returnType = signature.getReturnType(); // 返回值類型:void
Class declaringType = signature.getDeclaringType(); // 方法所在類名:MainActivity
String[] parameterNames = signature.getParameterNames(); // 參數(shù)名:view
Class[] parameterTypes = signature.getParameterTypes(); // 參數(shù)類型:View
//上面在編寫自定義注解時就聲明了兩個屬性嗅定,分別是value和type自娩,
//通過Method對象得到切點上的注解
TestAnnoTrace annotation = method.getAnnotation(TestAnnoTrace.class);
String value = annotation.value();
int type = annotation.type();
5.方法耗時計算的實現(xiàn)
因為@Around是環(huán)繞通知,可以在切點的前后分別執(zhí)行一些操作渠退,AspectJ為了能肯定操作是在切點前還是在切點后忙迁,所以在@Around通知中需要手動執(zhí)行joinPoint.proceed()來確定切點已經(jīng)執(zhí)行,故在joinPoint.proceed()之前的代碼會在切點執(zhí)行前執(zhí)行碎乃,在joinPoint.proceed()之后的代碼會切點執(zhí)行后執(zhí)行姊扔。于是,方法耗時計算的實現(xiàn)就是這么簡單:
@Around("pointcut()")
public void around(ProceedingJoinPoint joinPoint) throws Throwable {
long beginTime = SystemClock.currentThreadTimeMillis();
joinPoint.proceed();
long endTime = SystemClock.currentThreadTimeMillis();
long dx = endTime - beginTime;
System.out.println("耗時:" + dx + "ms");
}
五梅誓、動態(tài)代理實現(xiàn)AOP
動態(tài)代理也叫做JDK代理恰梢、接口代理。不需要實現(xiàn)目標對象的接口梗掰。生成代理對象嵌言,使用的是Java的API,動態(tài)的在內(nèi)存中構(gòu)件代理對象(這需要我們指定創(chuàng)建代理對象/目標對象的接口的類型)及穗。
在java的動態(tài)代理機制中摧茴,有兩個重要的類或接口,一個是 InvocationHandler(Interface)埂陆、另一個則是Proxy(Class)苛白,這一個類和接口是實現(xiàn)我們動態(tài)代理所必須用到的娃豹。
InvocationHandler:
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}
每一個代理實類例的invocation handler都要實現(xiàn)InvocationHandler這個接口。并且每個代理類的實例都關(guān)聯(lián)到了一個handler购裙,當我們通過代理對象調(diào)用一個方法的時候懂版,這個方法的調(diào)用就會被轉(zhuǎn)發(fā)為由InvocationHandler這個接口的 invoke 方法來進行調(diào)用。
- proxy:指代生成的代理對象躏率;
- method:指代的是我們所要調(diào)用真實對象的某個方法的Method對象躯畴;
- args:指代的是調(diào)用真實對象某個方法時接受的參數(shù);
我們來看看Proxy這個類薇芝,這個類的作用就是用來動態(tài)創(chuàng)建一個代理對象的類私股,它提供了許多的方法,但用的最多的就是 newProxyInstance 這個方法:
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
- loader:一個ClassLoader對象恩掷,定義了由哪個ClassLoader對象來對生成的代理對象進行加載
- interfaces:一個Interface對象的數(shù)組吴攒,表示的是我將要給我需要代理的對象提供一組什么接口瑟匆,如果我提供了一組接口給它,那么這個代理對象就宣稱實現(xiàn)了該接口(多態(tài))天通,這樣我就能調(diào)用這組接口中的方法了
- h:一個InvocationHandler對象克滴,表示的是當我這個動態(tài)代理對象在調(diào)用方法的時候逼争,會關(guān)聯(lián)到哪一個InvocationHandler對象上。
通過上面的需要傳入接口的參數(shù)可以看出劝赔,JDK動態(tài)代理需要借助接口來實現(xiàn)誓焦,如果我們要代理的對象功能沒有抽成任何接口,那么我們就無法通過JDK動態(tài)代理的方式來實現(xiàn)着帽。
好了杂伟,在介紹完這兩個接口(類)以后,我們來通過一個實例來看看我們的動態(tài)代理模式是什么樣的仍翰。首先我們定義了一個Subject類型的接口赫粥,為其聲明了兩個方法,這兩個方法表示被代理類需要實現(xiàn)的功能:
public interface Subject {
public void sayGoodBye();
public void sayHello(String str);
}
接著予借,定義了一個類來實現(xiàn)這個接口越平,這個類就是我們的真實對象(被代理類),RealSubject類:
public class RealSubject implements Subject {
@Override
public void sayGoodBye() {
System.out.println("RealSubject sayGoodBye");
}
@Override
public void sayHello(String str) {
System.out.println("RealSubject sayHello " + str);
}
}
下一步灵迫,我們就要定義一個InvocationHandler了秦叛,相當于一個代理處理器。前面說個瀑粥,每一個動態(tài)代理類實例的invocation handler 都必須要實現(xiàn) InvocationHandler 這個接口:
public class SubjectInvocationHandler implements InvocationHandler {
//這個就是我們要代理的真實對象
private Object subject;
//構(gòu)造方法挣跋,給我們要代理的真實對象賦初值
public SubjectInvocationHandler(Object subject) {
this.subject = subject;
}
@Override
public Object invoke(Object object, Method method, Object[] args) throws Throwable {
//在代理真實對象前我們可以添加一些自己的操作
System.out.println("before Method invoke");
System.out.println("Method:" + method);
//當代理對象調(diào)用真實對象的方法時,其會自動的跳轉(zhuǎn)到代理對象關(guān)聯(lián)的handler對象的invoke方法來進行調(diào)用
method.invoke(subject, args);
//在代理真實對象后我們也可以添加一些自己的操作
System.out.println("after Method invoke");
return null;
}
}
SubjectInvocationHandler并不是真正的代理類利凑,而是用于定義代理類需要擴展浆劲、增強那些方法功能的類嫌术。在invoke函數(shù)中,對代理對象的所有方法的調(diào)用都被轉(zhuǎn)發(fā)至該函數(shù)處理牌借。在這里可以靈活的自定義各種你能想到的邏輯度气。
最后,來看看我們的Client類:
public static void main(String[] args) {
//被代理類
Subject realSubject = new RealSubject();
//我們要代理哪個類膨报,就將該對象傳進去磷籍,最后是通過該被代理對象來調(diào)用其方法的
SubjectInvocationHandler handler = new SubjectInvocationHandler(realSubject);
//生成代理類
Subject subject = (Subject) Proxy.newProxyInstance(handler.getClass().getClassLoader(),
realSubject.getClass().getInterfaces(), handler);
//輸出代理類對象
System.out.println("Proxy : "+ subject.getClass().getName());
System.out.println("Proxy super : "+ subject.getClass().getSuperclass().getName());
System.out.println("Proxy interfaces : "+ subject.getClass().getInterfaces()[0].getName());
//調(diào)用代理類sayGoodBye方法
subject.sayGoodBye();
System.out.println("--------");
//調(diào)用代理類sayHello方法
subject.sayHello("Test");
}
輸出結(jié)果:
Proxy : com.sun.proxy.$Proxy0
Proxy super : java.lang.reflect.Proxy
Proxy interfaces : com.company.ha.Subject
before Method invoke
Method:public abstract void com.company.ha.Subject.sayGoodBye()
RealSubject sayGoodBye
after Method invoke
--------
before Method invoke
Method:public abstract void com.company.ha.Subject.sayHello(java.lang.String)
RealSubject sayHello Test
after Method invoke
如果我們定義的方法有返回值,那么可以通過invoke中把該方法的返回值進行返回现柠,因為返回值的對象是Object院领,所以支持返回值為空(void)的返回。
@Override
public Object invoke(Object object, Method method, Object[] args) throws Throwable {
//直接返回調(diào)用對象的返回值
return method.invoke(subject,args);
}
六够吩、動態(tài)代理應(yīng)用(Retrofit)
Android中的網(wǎng)絡(luò)注解框架retrofit內(nèi)部實現(xiàn)其實就是應(yīng)用了動態(tài)代理技術(shù)比然,通常我們定義網(wǎng)絡(luò)接口是這樣的:
public interface ApiStore {
// 員工登錄
@FormUrlEncoded
@POST("/resource/d/member/login")
Observable<BaseResponse<LoginResult>> login(@FieldMap Map<String, String> params);
// 退出登錄
@FormUrlEncoded
@POST("/resource/d/member/signOut")
Observable<BaseResponse<LogOutResult>> logout(@FieldMap Map<String, String> params);
//....
}
創(chuàng)建Retrofit:
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://ww.xxx.com/")
.build();
ApiStore service = retrofit.create(ApiStore.class);
我們可以看retrofit.create內(nèi)部具體的實現(xiàn)方式:
public <T> T create(final Class<T> service) {
//...
return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
new InvocationHandler() {
private final Platform platform = Platform.get();
@Override public Object invoke(Object proxy, Method method, @Nullable Object[] args)
throws Throwable {
// If the method is a method from Object then defer to normal invocation.
if (method.getDeclaringClass() == Object.class) {
return method.invoke(this, args);
}
if (platform.isDefaultMethod(method)) {
return platform.invokeDefaultMethod(method, service, proxy, args);
}
ServiceMethod<Object, Object> serviceMethod =
(ServiceMethod<Object, Object>) loadServiceMethod(method);
OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
return serviceMethod.callAdapter.adapt(okHttpCall);
}
});
}
看到?jīng)],retrofit就是通過Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },new InvocationHandler() )的方式創(chuàng)建的一個代理類周循,invoke中的代碼就是當網(wǎng)絡(luò)接口被調(diào)用的時候需要做的處理强法。在這個方法里,首先根據(jù)接口定義的方法湾笛,生成一個ServiceMethod對象饮怯,在ServiceMethod對象中會反射接口中定義的注解,解析出具體的網(wǎng)絡(luò)請求方式嚎研,然后拿到封裝好的ServiceMethod對象后蓖墅,構(gòu)造一個OkHttpCall對象,以便與進行真正的網(wǎng)絡(luò)請求(OkHttp)临扮。