名字看着有點繞, 但目的其實很簡單明確: 就是想實現動態代理的對象實例, 在運行時也可以切換. 先理解前提條件和程序上下文, 譬若有以下接口:java
public interface Responder {
void onMethod1(String s);
int onMethod2();
void onMethod3();
}
複製代碼
咱們將接口的一個實例Responder r1
傳入了一個別的類p1 = new Presenter(r1)
(或者外部SDK), 在運行時咱們生成了不一樣的Responder實例r2, 如今但願r2可以替換r1, 但對於Presenter類是沒法感知, 不用關心的. 顯然咱們的程序上下文可以實現對於Responder實例的控制(建立/傳遞), 但如今問題是Presenter類僅有構造參數對Responder的傳入, 沒有setResponder(Responder r)
這樣的方法(若是存在setResponder
這樣的方法, 但就沒這坨事了:). 能不能再建立一個Presenter實例p2再傳入r2呢? 若是程序上下文容許的話也沒這坨事了.bash
因此條件是這樣: 接口的不一樣實例須要傳入一個對象, 但這個對象持有的實例卻沒法更改, 同時這個對象也沒法再次建立.app
說這麼多不就是要用代理模式嗎? 不錯, 代理模式正是能夠解決這類問題的. 表述這麼累贅是想關注問題的場景, 而不是爲了生搬硬套模式.ide
因而一個簡單的代理類出來了:spa
public class ResponderWrapper implements Responder {
private final Responder impl;
public ResponderWrapper(Responder r) {
impl = r;
}
@Override
void onMethod1(String s) {
impl.onMethod1(s);
}
@Override
int onMethod2() {
return impl.onMethod2();
}
@Override
void onMethod3() {
impl.onMethod3();
}
}
複製代碼
由於還要動態的改變代理對象因此添加一個set方法:代理
void setResponder(Responder r) {
impl = r;
}
複製代碼
那麼傳入Presenter對象的實例就再也不是r1了, 而是code
wrapper = new ResponderWrapper(r1);
p1 = new Presenter(wrapper);
複製代碼
這時建立了新的Responder實例r2, 咱們只須要對象
wrapper.setResponder(r2);
複製代碼
就可以達到咱們的目的了! p1仍是p1, p1持有的實例仍是同一個實例, 在切換前p1調的是r1的實現, 切換後天然就調用了r2的實現. 這種代理就是很是常見的靜態代理, 僅就功能實現來講這已經徹底OK了, 沒有任何問題了. 是否是非得用動態代理? 並非!接口
那動態代理是幹嘛的? 爲了適應變化, 什麼的變化? 接口的變化! 若是接口Responder新增一個方法, ResponderWrapper再增長一樣一個接口; 若是修改Responder一個方法的參數, ResponderWrapper再接着修改並調用接口實例的新方法, 如此類推, 也沒任何問題. 但接口的方法一旦變的不少, 接口的實現類一旦變的不少, 就須要作大量繁瑣重複的工做, 那麼動態代理就可以解決這種重複繁瑣的工做. 以動態代理的形式寫一個ResponderWrapper很是簡單:get
public final class ResponderWrapper {
public static Responder wrap(Responder responder) {
return (Responder) Proxy.newProxyInstance(Responder.class.getClassLoader(),
Responder.class.getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return method.invoke(responder, args);
}
});
}
}
複製代碼
可是這樣寫沒法知足動態切換的需求, 因此咱們的最終目的這纔出來了: 以動態代理形式建立的代理實例可以動態切換持有的對象實例 但一旦ResponderWrapper.wrap
傳入r1那麼匿名對象持有的Responder對象就只能一直是r1, 因此但願method.invoke(responder, args)
這裏的responder
可以動態切換, 這種"動態"能力通常都是以接口的形式實現, 因而有:
public final class ResponderWrapper {
public interface Provider {
Responder get();
}
public static Responder wrap(final Provider provider) {
return (Responder) Proxy.newProxyInstance(Responder.class.getClassLoader(),
Responder.class.getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return method.invoke(provider.get(), args);
}
});
}
複製代碼
程序上下文實現ResponderWrapper.Provider
接口, 當接口方法被調用時返回的實例是當前的Responder, 不用關心何時切換:
mResonder = r1;
wrapper = ResponderWrapper.wrap(new ResponderWrapper.Provider() {
@Override
public ResponderWrapper.Responder get() {
return mResponder;
}
});
p1 = new Presenter(wrapper);
...
mResonder = r2;
複製代碼
若是以爲接口過重, 其實這種形式也徹底能夠不用接口的方式實現, 由於咱們最終須要的實際上是一個Responder實例, 在接口方法被調用的時候可以調用這個實例的對應的方法而已, 因此能夠寫成這樣:
public final class ResponderWrapper {
public static final class Holder {
public Responder responder;
}
public static Responder wrap(final Holder holder) {
return (Responder) Proxy.newProxyInstance(Responder.class.getClassLoader(),
Responder.class.getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return method.invoke(holder.responder, args);
}
});
}
}
複製代碼
程序上下文持有ResponderWrapper.Holder的實例, 再在須要的時候設置不一樣的Resonder實例:
mHolder = new ResponderWrapper.Holder(r1);
wrapper = ResponderWrapper.wrap(holder)
p1 = new Presenter(wrapper);
...
mHolder.responder = r2
複製代碼
若是用範型抽象全部接口類, 就能夠寫的更通用一點:
public final class ResponderWrapper {
public static final class Holder<T> {
public T responder;
}
@SuppressWarnings("unchecked")
public static <T> T wrap(Holder<T> holder) {
T r = holder.responder;
return (T) Proxy.newProxyInstance(r.getClass().getClassLoader(),
r.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return method.invoke(holder.responder, args);
}
});
}
}
複製代碼
這裏臨時利用holder.responder
來獲取ClassLoade
r和Class<?>[]
, 也徹底能夠將Class對象傳入:
public final class ResponderWrapper {
public static final class Holder<T> {
public T responder;
}
@SuppressWarnings("unchecked")
public static <T> T wrap(Holder<T> holder, Class<T> clazz) {
return (T) Proxy.newProxyInstance(clazz.getClassLoader(),
clazz.getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return method.invoke(holder.responder, args);
}
});
}
}
複製代碼
這就是咱們所謂的動態切換的動態代理了.