前言
本文會(huì)簡(jiǎn)單介紹下 Java 中動(dòng)態(tài)代理模式的使用跟衅,然后著重分享下動(dòng)態(tài)代理如何在提高代碼靈活性方面大展身手。文中會(huì)列舉兩個(gè)實(shí)例蹋绽,一個(gè)是在 MVP 中如何巧妙解決 Presenter 中頻繁使用 if (getView() != null) { }
這種重復(fù)代碼的問題;另一個(gè)是在項(xiàng)目中如何讓多個(gè) modules 間解耦更加靈活、更加純粹的問題郭厌。
動(dòng)態(tài)代理的基本使用
要使用動(dòng)態(tài)代理,主要涉及到兩個(gè)類雕蔽,一個(gè)是 Proxy
類折柠,一個(gè)是 InvocationHandler
類。在介紹如何使用之前批狐,需要明確的是:動(dòng)態(tài)代理的代理對(duì)象只能是 Interface
扇售,不能是 Class
,也不能是 abstract class
嚣艇。這是因?yàn)樗袆?dòng)態(tài)生成的代理類都繼承自 Proxy
承冰。而 java 是單繼承的,所以只有接口對(duì)象能被動(dòng)態(tài)代理食零。
回到剛才介紹的兩個(gè)類困乒,Proxy
描述了一個(gè)代理對(duì)象,同時(shí)它提供了創(chuàng)建并實(shí)例化一個(gè)代理對(duì)象的靜態(tài)方法贰谣。InvocationHandler
是一個(gè)代理對(duì)象的調(diào)用處理器娜搂,它只有一個(gè) invoke
方法,所有被代理的對(duì)象的方法調(diào)用都會(huì)通過這個(gè)方法執(zhí)行吱抚,我們的代理行為也就是在這個(gè)方法里面實(shí)現(xiàn)的百宇。下面給出一個(gè)很簡(jiǎn)單的示例:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class DemonstrationProxy {
interface A {
void method();
}
static class AIpml implements A {
@Override
public void method() {
System.out.println("method in AIpml");
}
}
//動(dòng)態(tài)代理對(duì)象
static class AProxy implements InvocationHandler {
//被代理的對(duì)象實(shí)例
final Object origin;
AProxy(Object origin) {
this.origin = origin;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("start to invoke method " + method.getName() + " proxy is " + proxy.getClass().getCanonicalName());
//執(zhí)行實(shí)際對(duì)象的方法
return method.invoke(origin, args);
}
}
public static void main(String[] args) {
final A a = new AIpml();
final InvocationHandler handler = new AProxy(a);
final A proxyA = (A) Proxy.newProxyInstance(a.getClass().getClassLoader(), a.getClass().getInterfaces(), handler);
proxyA.method();
}
}
編譯運(yùn)行后你將看到如下輸出:
start to invoke method proxy is $Proxy0
method in AIpml
MVP 中如何精簡(jiǎn)不必要的代碼
在 MVP 的開發(fā)模式中,Presenter 持有 View 的引用秘豹,當(dāng)我們需要與 View 進(jìn)行交互時(shí)恳谎,通過 getView()
方法獲得 View 對(duì)象。為了避免內(nèi)存泄漏憋肖,在不需要 Presenter 的時(shí)候(比如在 Activity 的 onDestroyed()
生命周期)將 View 對(duì)象置空因痛。然而此時(shí)可能一些異步任務(wù)沒有結(jié)束,當(dāng)它們結(jié)束后岸更,getView()
就會(huì)返回 null 鸵膏。為了避免 NPE,通常的做法是在所有異步任務(wù)里需要訪問 View 的地方怎炊,都要進(jìn)行 if(getView() != null) { }
這樣的檢查谭企。當(dāng)你寫了好幾個(gè) Presenter 之后廓译,便會(huì)發(fā)現(xiàn)這是一件很煩的事情,不僅僅是因?yàn)槊看我獙懲瑯拥臇|西债查,還有就是:
It's always a bad sign when the else branch is missing.
關(guān)于這個(gè)問題非区,在 Medium 上也有討論,上面也列出了一些解決方案盹廷。比如用 ThirtyInch
這個(gè)第三方 MVP 框架征绸,它把所有對(duì) View 的操作封裝為一個(gè)個(gè)的 ViewAction
,TiPresenter
內(nèi)部會(huì)管理這些 ViewAction
的運(yùn)行俄占。只有在 View attach 到 Presenter 的時(shí)候管怠,才會(huì)執(zhí)行 ViewAction
,否則會(huì)保留 ViewAction
直到 View 再次 attach 到 Presenter缸榄。還有就是用 WeakReference
或者 Optional
來管理 View 渤弛。對(duì)于第一個(gè),算是一個(gè)不錯(cuò)的解決方案甚带,但它作為一個(gè)框架她肯,使用它有一定的引入成本,還有另一個(gè)弊端就是 Presenter 和 View 的生命周期綁定得更加緊密鹰贵,增加了 ViewAction
的維護(hù)成本 晴氨。對(duì)于第二個(gè)方案,感覺像是轉(zhuǎn)移話題一樣砾莱,并沒有解決什么根本問題瑞筐。
其實(shí)這個(gè)問題的源頭在于 getView()
方法是 nullable 的凄鼻,如果該方法返回的 View 能確保非空腊瑟,而且不存在內(nèi)存泄漏問題,且無論是 View 處于哪種生命周期代碼都能得到正確的調(diào)用块蚌,那么問題就解決了闰非。
此時(shí)就輪到動(dòng)態(tài)代理出場(chǎng)了,當(dāng) View 還沒結(jié)束的時(shí)候峭范,getView()
對(duì)象返回的是真實(shí)的 View 對(duì)象财松,而當(dāng) View 的生命周期結(jié)束后,getView()
對(duì)象只需要返回一個(gè)代理 View 即可纱控,這樣就確保了 getView()
不會(huì)返回一個(gè)空的對(duì)象辆毡,自然就不需要反復(fù)檢查,而且代理對(duì)象并不會(huì)對(duì)真實(shí)的 View 有任何的影響甜害,所以代碼邏輯也不會(huì)有任何問題舶掖。
Talk is cheap. Show me the code.
public class AbsPresenter<View extends IView> implements IPresenter {
private View mView;
private Class<? extends IView> mViewClass;
public AbsPresenter(@NonNull View iView) {
this.mView = iView;
this.mViewClass = iView.getClass();
if (this.mViewClass.getInterfaces().length == 0) {
throw new IllegalArgumentException("iView must implement IView interface");
}
}
public void detach() {
this.mView = null;
}
public @NonNull View getView() {
if (mView == null) {
return ViewProxy.newInstance(mViewClass);
}
return mView;
}
private static final class ViewProxy implements InvocationHandler {
public static <View> View newInstance(Class<? extends IView> clazz) {
return (View) Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), new ViewProxy());
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Type type = method.getReturnType();
if (type == boolean.class) {
return false;
} else if (type == int.class) {
return 0;
} else if (type == short.class) {
return (short)0;
} else if(type == char.class) {
return (char)0;
} else if (type == byte.class) {
return (byte)0;
} else if(type == long.class) {
return 0L;
} else if (type == float.class) {
return 0f;
} else if (type == double.class) {
return 0D;
} else {
return null;
}
}
}
}
這就是我在流利說項(xiàng)目里抽象出的 Presenter,主要展示了 getView()
方法是怎么利用動(dòng)態(tài)代理保證返回值為非空尔店。如此一來眨攘,就大可放心地在 Presenter 中使用 getView()
方法主慰,而不用擔(dān)心 NPE ,也不用擔(dān)心內(nèi)存泄漏鲫售,代碼還能更干凈共螺,一舉三得!
模塊間結(jié)構(gòu)如何更加靈活情竹,更加精簡(jiǎn)
流利說項(xiàng)目里有很多代表不同功能的模塊藐不,為了將模塊間解耦,我們?cè)谝粋€(gè)公共的模塊中定義各個(gè)功能模塊對(duì)外開放的接口鲤妥,并在各自模塊中實(shí)現(xiàn)佳吞。此外還需要一個(gè)類 (以下稱作 ModuleProvider) 管理這些模塊接口,它需要向外提供接口的 set
和 get
方法棉安。在 Application 初始化的時(shí)候底扳,通過反射將這些接口實(shí)例化,然后各個(gè)模塊便可以通過 ModuleProvider 的 get
方法獲取其他模塊的接口贡耽。一切看起來既美好又和諧衷模,可是這里有個(gè)問題,如果我正在開發(fā) A 模塊蒲赂,為了更快的編譯速度阱冶,我在 build.gradle 中去掉了 B 模塊,ModuleProvider.getB()
將會(huì)返回為 null 滥嘴,那么 A 模塊中很多地方都會(huì)出現(xiàn) NPE 木蹬。當(dāng)然這可以通過判空解決,但顯然很蠢若皱,而且如此一來镊叁,代碼豈不是解耦地不徹底?
一開始的做法是對(duì)于所有的接口都有一個(gè)默認(rèn)的空實(shí)現(xiàn)走触,對(duì)應(yīng)到上面的例子就是 ModuleProvider.getB()
會(huì)有兩個(gè)不同的返回結(jié)果晦譬,一個(gè)是在 B 模塊內(nèi)對(duì)接口的實(shí)現(xiàn),另一個(gè)是在公共模塊的一個(gè)空實(shí)現(xiàn) 互广。如此一來敛腌,ModuleProvider.getB()
方法就變成了這樣:
public static B getB() {
if (b == null) {
b = new EmptyB();
}
return b;
}
現(xiàn)在隨意去掉不想要的模塊也能愉快地敲代碼了。
這樣的實(shí)現(xiàn)看起來已經(jīng)很不錯(cuò)了惫皱,可還是有優(yōu)化的空間像樊。問題在于每一個(gè)接口都有兩個(gè)實(shí)現(xiàn),每次要對(duì)接口作修改的時(shí)候旅敷,要同時(shí)維護(hù)兩個(gè)實(shí)現(xiàn)類生棍,而且其中一個(gè)實(shí)現(xiàn)并沒有實(shí)際的作用,更多地是只想把精力放在模塊中的實(shí)現(xiàn)上扫皱。這個(gè)時(shí)候動(dòng)態(tài)代理又可以大顯身手了足绅。既然只是一個(gè)空實(shí)現(xiàn)捷绑,那么當(dāng)模塊不存在時(shí)返回一個(gè)接口的動(dòng)態(tài)代理不就好了嗎?最重要的是氢妈,現(xiàn)在不需要同時(shí)維護(hù)兩個(gè)實(shí)現(xiàn)粹污,可以集中精力在有意義的改動(dòng)上。為此我在項(xiàng)目中提供了一個(gè)生成接口代理的工具類:
import com.xxx.xxx.annotations.SpecifyBooleanValue;
import com.xxx.xxx.annotations.SpecifyClassValue;
import com.xxx.xxx.annotations.SpecifyIntegerValue;
import com.xxx.xxx.annotations.SpecifyStringValue;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class EmptyModuleProxy implements InvocationHandler {
public static <T> T newInstance(Class<T> clazz) {
return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, new EmptyModuleProxy());
}
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
final SpecifyClassValue specifyClassValue = method.getAnnotation(SpecifyClassValue.class);
if (specifyClassValue != null) return specifyClassValue.returnValue();
final SpecifyIntegerValue specifyIntegerValue = method.getAnnotation(SpecifyIntegerValue.class);
if (specifyIntegerValue != null) return specifyIntegerValue.returnValue();
final SpecifyStringValue specifyStringValue = method.getAnnotation(SpecifyStringValue.class);
if(specifyStringValue != null) return specifyStringValue.returnValue();
final SpecifyBooleanValue specifyBooleanValue = method.getAnnotation(SpecifyBooleanValue.class);
if(specifyBooleanValue != null) return specifyBooleanValue.returnValue();
return defaultValueByType(method.getReturnType());
}
private Object defaultValueByType(Class type) {
if (type == boolean.class) {
return false;
} else if (type == int.class) {
return 0;
} else if (type == short.class) {
return (short)0;
} else if(type == char.class) {
return (char)0;
} else if (type == byte.class) {
return (byte)0;
} else if(type == long.class) {
return 0L;
} else if (type == float.class) {
return 0f;
} else if (type == double.class) {
return 0D;
} else {
return null;
}
}
}
這樣一來ModuleProvider.getB()
方法就變成這樣:
public static B getB() {
if (b == null) {
b = EmptyModuleProxy.newInstance(B.class);
}
return b;
}
至此首量,通過動(dòng)態(tài)代理完美地解決了問題壮吩。注意到 EmptyModuleProxy
中還有很多注解,這是因?yàn)楫?dāng)一些模塊沒有引入的時(shí)候加缘,希望它的某些接口能返回一些指定的值以方便測(cè)試鸭叙,所以額外定義了一些注解來解決這個(gè)問題。比如 B 接口里面定義了一個(gè) getBoolean()
方法拣宏,默認(rèn)返回的是 false 沈贝,但實(shí)際上我希望在沒有引入 B 模塊的時(shí)候返回 true。那么 B 接口就可以做如下聲明:
public interface B {
@SpecifyBooleanValue(returnValue = true)
Class<?> getBoolean();
}
總結(jié)
通過 MVP 和模塊間解耦這兩個(gè)實(shí)際項(xiàng)目中的例子勋乾,能夠充分地說明動(dòng)態(tài)代理技術(shù)的運(yùn)用宋下,能夠給我們的代碼帶來很多靈活性,讓一些實(shí)現(xiàn)變得更加簡(jiǎn)潔辑莫、也更加優(yōu)雅学歧。當(dāng)然動(dòng)態(tài)代理能帶給我們的不僅僅是靈活性。比如 Retrofit
就通過動(dòng)態(tài)代理將我們聲明的各種 Service 接口轉(zhuǎn)換為一個(gè)個(gè)的 ServiceMethod 各吨,然后交給 OkHttp
執(zhí)行具體的網(wǎng)絡(luò)操作枝笨,從而讓網(wǎng)絡(luò)請(qǐng)求變得如此優(yōu)雅自然。所以說合理地運(yùn)用這項(xiàng)技術(shù)揭蜒,能讓你把代碼敲地更嗨横浑!