面向切面編程(Aspect-Oriented Programming)搂根,是Java開發(fā)中常見的編程方式,很多著名的開源框架都是基于AOP思想實現铃辖,比如Spring剩愧、Retrofit、Mybatis等娇斩,實現AOP的方式有很多種仁卷,主要分為三類:靜態(tài)編譯
穴翩、編譯期注入代碼
、動態(tài)編譯
锦积,前兩者都是在生成jar芒帕、dex包之前就完成代碼處理,動態(tài)編譯是在運行時實現丰介,比如補丁就是動態(tài)編譯實現背蟆。
示例圖
.java
<靜態(tài)編譯>
.class
<編譯期注入代碼>
.dex
運行時
<動態(tài)編譯與動態(tài)生成字節(jié)碼、動態(tài)代理>
分類
我在這里總結了10種AOP的方式
名稱 | 說明 | 支持 |
---|---|---|
APT | 靜態(tài)編譯 | Java基矮、Android |
AspectJ | 使用專門的編譯器淆储,在編譯期插入代碼 | Java、Android |
Javassist | 動態(tài)編譯與動態(tài)生成字節(jié)碼 | Java家浇、Android |
CGLIB | 動態(tài)編譯與動態(tài)生成字節(jié)碼 | Java |
ByteBuddy | 動態(tài)編譯與動態(tài)生成字節(jié)碼 | Java、Android |
JDK動態(tài)代理 | 動態(tài)代理 | Java碴裙、Android |
ASM | 動態(tài)編譯與動態(tài)生成字節(jié)碼 | Java钢悲、Android |
ASMDEX | 動態(tài)編譯與動態(tài)生成字節(jié)碼 | Android |
DexMaker | 動態(tài)編譯與動態(tài)生成字節(jié)碼 | Android |
Xposed | 需要root權限,在運行時插入字節(jié)碼 | Android |
APT(Annotation Processing Tool)
APT技術Java應用并不是特別廣泛舔株,但是在Android中是主要實現AOP的方式之一莺琳,通過注解實現在編譯期間生成代碼,執(zhí)行效率高载慈。
代表框架:ButterKnife惭等、GRouter、AptPreferences
教程
AspectJ
使用AspectJ編譯器(ajc)办铡,在編譯時期辞做,在關鍵的的地方插入部分代碼,處理相關邏輯寡具,比如可以用于打印方法執(zhí)行的效率秤茅,權限檢查等,Spring就使用了AspectJ童叠。在Android上的應用主要是做性能監(jiān)控框喳、基于注解的數據埋點等。
代表框架:Hugo厦坛、Spring
教程
AspectJ AOP教程:實現Android基于注解無侵入埋點五垮、性能監(jiān)控
CGLIB
CGLIB 是一個強大的,高性能杜秸,高質量的Code生成類庫 放仗,是基于ASM封裝,大名鼎鼎的Hibernate就是基于CGLIB實現亩歹,CGLIB是一個應用非常廣泛的AOP框架匙监。
代表框架:Hibernate
+--- cglib:cglib:3.3.0
| \--- org.ow2.asm:asm:7.1
教程
添加依賴
dependencies {
implementation 'cglib:cglib:3.3.0'
}
代碼
public class CglibTest implements MethodInterceptor {
public static void main(String[] args) {
User user = (User) new CglibTest().getProxy(User.class);
user.setId(1);
user.getId();
}
public Enhancer enhancer = new Enhancer();
private Object getProxy(Class clazz) {
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
return enhancer.create();
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("執(zhí)行開始:" + method);
Object result = proxy.invokeSuper(obj, args);
System.out.println("執(zhí)行結束:" + method);
return result;
}
public static class User {
private int id;
public int getId() {return id;}
public void setId(int id) {this.id = id;}
}
}
結果
執(zhí)行開始:public void com.thejoyrun.aptpreferences.CglibTest$User.setId(int)
執(zhí)行結束:public void com.thejoyrun.aptpreferences.CglibTest$User.setId(int)
執(zhí)行開始:public int com.thejoyrun.aptpreferences.CglibTest$User.getId()
執(zhí)行結束:public int com.thejoyrun.aptpreferences.CglibTest$User.getId()
ByteBuddy
Byte Buddy 是一個運行時動態(tài)生成字節(jié)碼的庫凡橱,它是依賴Java虛擬機,由于Android的字節(jié)碼和Java不同亭姥,也提供了Android的支持包稼钩,大名鼎鼎的測試框架 Mockito 就是基于ByteBuddy實現,Mockito3也正式支持在Android運行時環(huán)境使用达罗,但是包大小為3.3MB坝撑,建議只在debug環(huán)境下使用。
api 'net.bytebuddy:byte-buddy:1.10.1'
api 'net.bytebuddy:byte-buddy-android:1.10.1'
代表框架:AopPreferences粮揉、Mockito
教程
添加依賴
dependencies {
implementation 'net.bytebuddy:byte-buddy:1.10.1'
// implementation 'net.bytebuddy:byte-buddy-android:1.10.1'
}
代碼
public class ByteBuddyTest {
public static void main(String[] args) {
User user = new ByteBuddyTest().getProxy(User.class);
user.setId(1);
user.getId();
}
private <T> T getProxy(Class<T> clazz) {
Class<? extends T> loaded = new ByteBuddy().subclass(clazz)
.method(ElementMatchers.<MethodDescription>any())
.intercept(MethodDelegation.to(ByteBuddyTest.class))
.make().load(getClass().getClassLoader()).getLoaded();
// 如果是 Android
// .load(getClass().getClassLoader(), new AndroidClassLoadingStrategy.Wrapping(StorageAndroid.context.getDir("dexgen", Context.MODE_PRIVATE))).getLoaded();
try {
return loaded.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
@RuntimeType
public static Object intercept(@This Object proxy, @Origin Method method, @SuperCall Callable<?> callable) throws Exception {
System.out.println("執(zhí)行開始:" + method);
Object result = callable.call();
System.out.println("執(zhí)行結束:" + method);
return result;
}
public static class User {
private int id;
public int getId() {return id;}
public void setId(int id) {this.id = id;}
}
}
結果
執(zhí)行開始:public void com.thejoyrun.aptpreferences.ByteBuddyTest$User.setId(int)
執(zhí)行結束:public void com.thejoyrun.aptpreferences.ByteBuddyTest$User.setId(int)
執(zhí)行開始:public int com.thejoyrun.aptpreferences.ByteBuddyTest$User.getId()
執(zhí)行結束:public int com.thejoyrun.aptpreferences.ByteBuddyTest$User.getId()
JDK動態(tài)代理
JDK動態(tài)代理是基于JDK自帶實現AOP的方式巡李,缺點就是只支持接口代理。
教程
由于是基于JDK開發(fā)侨拦,所以不需要引入第三方包
public class InvocationHandlerTest implements InvocationHandler {
public static void main(String[] args) {
User user = new InvocationHandlerTest().getProxy(User.class);
user.setId(1);
System.out.println("Id: " + user.getId());
}
private <T> T getProxy(Class<T> clazz) {
T result = (T) Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{clazz}, new InvocationHandlerTest());
return result;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("執(zhí)行開始:" + method);
if (method.getName().startsWith("set")) {
System.out.println("參數:" + args[0]);
} else {
Object result = new Random().nextInt();
System.out.println("執(zhí)行結果:" + result);
return result;
}
return null;
}
public interface User {
int getId();
void setId(int id);
}
}
結果
執(zhí)行開始:public abstract void com.thejoyrun.aptpreferences.InvocationHandlerTest$User.setId(int)
參數:1
執(zhí)行開始:public abstract int com.thejoyrun.aptpreferences.InvocationHandlerTest$User.getId()
執(zhí)行結果:165584246
Id: 165584246
ASM
ASM 是一個 Java 字節(jié)碼操控框架,直接操作字節(jié)碼指令辐宾,執(zhí)行效率高狱从,要是使用者掌握Java類字節(jié)碼文件格式及指令,對使用者的要求比較高叠纹。所以不建議直接使用季研,建議使用更加高階的Javassist框架。
ASMDEX
ASMDEX 是SAM開發(fā)誉察,針對Android字節(jié)碼的框架与涡。
Javassist
Javassist是基于ASM,提供了更高級的API持偏,執(zhí)行效率比ASM差一些驼卖,但無需掌握字節(jié)碼指令的知識,對使用者要求較低综液,多數的Android熱修復框架都是基于Javassist實現款慨。
代表框架:InstantRun、HotFix谬莹。
教程
Javassist 實現AOP檩奠、動態(tài)創(chuàng)建代碼
DexMaker
DexMaker 用于執(zhí)行針對Dalvik VM的編譯時或運行時代碼生成的Java語言API,類似CGLIB和ASM附帽。
Xposed
Xposed框架是一套開源的埠戳、在Android高權限模式下運行的框架服務,可以在不修改APK文件的情況下影響程序運行(修改系統)的框架服務蕉扮,基于它可以制作出許多功能強大的模塊整胃,且在功能不沖突的情況下同時運作,通常是用于實現特殊的編程喳钟,比如逆向工程屁使,自動化測試在岂。