前言
本文會(huì)簡(jiǎn)單介紹下 Java 中動(dòng)態(tài)代理模式的使用,然后著重分享下動(dòng)態(tài)代理如何在提高代碼靈活性方面大展身手。文中會(huì)列舉兩個(gè)實(shí)例,一個(gè)是在 MVP 中如何巧妙解決 Presenter 中頻繁使用 if (getView() != null) { } 這種重復(fù)代碼的問(wèn)題;另一個(gè)是在項(xiàng)目中如何讓多個(gè) modules 間解耦更加靈活、更加純粹的問(wèn)題。
動(dòng)態(tài)代理的基本使用
要使用動(dòng)態(tài)代理,主要涉及到兩個(gè)類(lèi),一個(gè)是 Proxy 類(lèi),一個(gè)是 InvocationHandler 類(lèi)。在介紹如何使用之前,需要明確的是:動(dòng)態(tài)代理的代理對(duì)象只能是 Interface,不能是 Class ,也不能是 abstract class。這是因?yàn)樗袆?dòng)態(tài)生成的代理類(lèi)都繼承自 Proxy。而 java 是單繼承的,所以只有接口對(duì)象能被動(dòng)態(tài)代理。
回到剛才介紹的兩個(gè)類(lèi),Proxy 描述了一個(gè)代理對(duì)象,同時(shí)它提供了創(chuàng)建并實(shí)例化一個(gè)代理對(duì)象的靜態(tài)方法。InvocationHandler 是一個(gè)代理對(duì)象的調(diào)用處理器,它只有一個(gè) invoke 方法,所有被代理的對(duì)象的方法調(diào)用都會(huì)通過(guò)這個(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 的開(kāi)發(fā)模式中,Presenter 持有 View 的引用,當(dāng)我們需要與 View 進(jìn)行交互時(shí),通過(guò) getView() 方法獲得 View 對(duì)象。為了避免內(nèi)存泄漏,在不需要 Presenter 的時(shí)候(比如在 Activity 的 onDestroyed() 生命周期)將 View 對(duì)象置空。然而此時(shí)可能一些異步任務(wù)沒(méi)有結(jié)束,當(dāng)它們結(jié)束后,getView() 就會(huì)返回 null 。為了避免 NPE,通常的做法是在所有異步任務(wù)里需要訪問(wèn) View 的地方,都要進(jìn)行 if(getView() != null) { } 這樣的檢查。當(dāng)你寫(xiě)了好幾個(gè) Presenter 之后,便會(huì)發(fā)現(xiàn)這是一件很煩的事情,不僅僅是因?yàn)槊看我獙?xiě)同樣的東西,還有就是:
It's always a bad sign when the else branch is missing.
關(guān)于這個(gè)問(wèn)題,在 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 來(lái)管理 View 。對(duì)于第一個(gè),算是一個(gè)不錯(cuò)的解決方案,但它作為一個(gè)框架,使用它有一定的引入成本,還有另一個(gè)弊端就是 Presenter 和 View 的生命周期綁定得更加緊密,增加了 ViewAction 的維護(hù)成本 。對(duì)于第二個(gè)方案,感覺(jué)像是轉(zhuǎn)移話(huà)題一樣,并沒(méi)有解決什么根本問(wèn)題。
其實(shí)這個(gè)問(wèn)題的源頭在于 getView() 方法是 nullable 的,如果該方法返回的 View 能確保非空,而且不存在內(nèi)存泄漏問(wèn)題,且無(wú)論是 View 處于哪種生命周期代碼都能得到正確的調(diào)用,那么問(wèn)題就解決了。
此時(shí)就輪到動(dòng)態(tài)代理出場(chǎng)了,當(dāng) View 還沒(méi)結(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ì)有任何問(wèn)題。
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;
}
}
}
}
這就是我在流利說(shuō)項(xiàng)目里抽象出的 Presenter,主要展示了 getView() 方法是怎么利用動(dòng)態(tài)代理保證返回值為非空。如此一來(lái),就大可放心地在 Presenter 中使用 getView() 方法,而不用擔(dān)心 NPE ,也不用擔(dān)心內(nèi)存泄漏,代碼還能更干凈,一舉三得!
模塊間結(jié)構(gòu)如何更加靈活,更加精簡(jiǎn)
流利說(shuō)項(xiàng)目里有很多代表不同功能的模塊,為了將模塊間解耦,我們?cè)谝粋€(gè)公共的模塊中定義各個(gè)功能模塊對(duì)外開(kāi)放的接口,并在各自模塊中實(shí)現(xiàn)。此外還需要一個(gè)類(lèi) (以下稱(chēng)作 ModuleProvider) 管理這些模塊接口,它需要向外提供接口的 set 和 get 方法。在 Application 初始化的時(shí)候,通過(guò)反射將這些接口實(shí)例化,然后各個(gè)模塊便可以通過(guò) ModuleProvider 的 get 方法獲取其他模塊的接口。一切看起來(lái)既美好又和諧,可是這里有個(gè)問(wèn)題,如果我正在開(kāi)發(fā) A 模塊,為了更快的編譯速度,我在 build.gradle 中去掉了 B 模塊,ModuleProvider.getB() 將會(huì)返回為 null ,那么 A 模塊中很多地方都會(huì)出現(xiàn) NPE 。當(dāng)然這可以通過(guò)判空解決,但顯然很蠢,而且如此一來(lái),代碼豈不是解耦地不徹底?
一開(kāi)始的做法是對(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) 。如此一來(lái),ModuleProvider.getB()方法就變成了這樣:
public static B getB() {
if (b == null) {
b = new EmptyB();
}
return b;
}
現(xiàn)在隨意去掉不想要的模塊也能愉快地敲代碼了。
這樣的實(shí)現(xiàn)看起來(lái)已經(jīng)很不錯(cuò)了,可還是有優(yōu)化的空間。問(wèn)題在于每一個(gè)接口都有兩個(gè)實(shí)現(xiàn),每次要對(duì)接口作修改的時(shí)候,要同時(shí)維護(hù)兩個(gè)實(shí)現(xiàn)類(lèi),而且其中一個(gè)實(shí)現(xiàn)并沒(méi)有實(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è)生成接口代理的工具類(lèi):
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;
}
}
}
這樣一來(lái)ModuleProvider.getB() 方法就變成這樣:
public static B getB() {
if (b == null) {
b = EmptyModuleProxy.newInstance(B.class);
}
return b;
}
至此,通過(guò)動(dòng)態(tài)代理完美地解決了問(wèn)題。注意到 EmptyModuleProxy 中還有很多注解,這是因?yàn)楫?dāng)一些模塊沒(méi)有引入的時(shí)候,希望它的某些接口能返回一些指定的值以方便測(cè)試,所以額外定義了一些注解來(lái)解決這個(gè)問(wèn)題。比如 B 接口里面定義了一個(gè) getBoolean() 方法,默認(rèn)返回的是 false ,但實(shí)際上我希望在沒(méi)有引入 B 模塊的時(shí)候返回 true。那么 B 接口就可以做如下聲明:
public interface B {
@SpecifyBooleanValue(returnValue = true)
Class<?> getBoolean();
}
總結(jié)
通過(guò) MVP 和模塊間解耦這兩個(gè)實(shí)際項(xiàng)目中的例子,能夠充分地說(shuō)明動(dòng)態(tài)代理技術(shù)的運(yùn)用,能夠給我們的代碼帶來(lái)很多靈活性,讓一些實(shí)現(xiàn)變得更加簡(jiǎn)潔、也更加優(yōu)雅。當(dāng)然動(dòng)態(tài)代理能帶給我們的不僅僅是靈活性。比如 Retrofit 就通過(guò)動(dòng)態(tài)代理將我們聲明的各種 Service 接口轉(zhuǎn)換為一個(gè)個(gè)的 ServiceMethod ,然后交給 OkHttp 執(zhí)行具體的網(wǎng)絡(luò)操作,從而讓網(wǎng)絡(luò)請(qǐng)求變得如此優(yōu)雅自然。所以說(shuō)合理地運(yùn)用這項(xiàng)技術(shù),能讓你把代碼敲地更嗨!