動機(jī)
學(xué)習(xí)動機(jī)來源于RxCache,在研究這個庫的源碼時,被這個庫的設(shè)計(jì)思路吸引了,該庫的原理就是通過動態(tài)代理和Dagger的依賴注入,實(shí)現(xiàn)Android移動端Retrofit的緩存功能。
既然在項(xiàng)目中嘗試使用這個庫,當(dāng)然要從設(shè)計(jì)的角度思考作者的思路,動態(tài)代理必然涉及到Java的反射,既然是反射,性能當(dāng)然會有所降低,那么是否有更好的思路呢,使用動態(tài)代理的優(yōu)勢有哪些?
關(guān)于動態(tài)代理,百度上面的資料數(shù)不勝數(shù),今天也借鑒其他前輩的學(xué)習(xí)總結(jié),自己實(shí)踐一次代理的實(shí)現(xiàn)。
靜態(tài)代理
代理分為動態(tài)代理和靜態(tài)代理,我們先看靜態(tài)代理的代碼:
我們首先定義一個接口:
public interface Subject {
void enjoyMusic();
}
我們接下來實(shí)現(xiàn)一個Subject的實(shí)現(xiàn)類:
public class RealSubject implements Subject {
@Override
public void enjoyMusic() {
System.out.println("enjoyMusic");
}
}
在不考慮代理模式的情況下,我們調(diào)用Subject的真實(shí)對象,我們代碼中必然是這樣:
@Test
public void testNoProxy() throws Exception {
Subject subject= new RealSubject();
subject.enjoyMusic();
}
上面是我們的業(yè)務(wù)代碼,我們這樣使用當(dāng)然沒有問題,但是我們需要考慮的一點(diǎn)是,如果我們的業(yè)務(wù)代碼中多次引用了這個類,并且在之后的版本迭代中,我們需要修改(或者替換)這個類,我們需要在引用這個對象的代碼處進(jìn)行修改——也就是說我們需要修改業(yè)務(wù)代碼。
這顯然不是良好的設(shè)計(jì),我們希望業(yè)務(wù)代碼不需要修改的前提下,進(jìn)行RealSubject的修改(或者替換),這時我們可以通過代理模式,創(chuàng)建一個代理類,從而達(dá)到控制RealSubject對象的引用 :
public class SubjectProxy implements Subject {
private Subject subject = new RealSubject();
@Override
public void enjoyMusic() {
subject.enjoyMusic();
}
}
我們在業(yè)務(wù)代碼中通過代理類,達(dá)到調(diào)用真實(shí)對象RealSubject的對應(yīng)方法:
@Test
public void staticProxy() throws Exception {
SubjectProxy proxy = new SubjectProxy();
proxy.enjoyMusic();
}
這就是靜態(tài)代理,優(yōu)勢是顯然的,如果我們需要一個新的對象NewRealSubject代替RealSubject 應(yīng)用在業(yè)務(wù)中,我們不需要修改業(yè)務(wù)代碼,而是只需要在代理類中,將代碼進(jìn)行簡單的替換:
private Subject subject = new RealSubject();//before
private Subject subject = new NewRealSubject();//after
同理,即使RealSubject類有所修改(比如說構(gòu)造函數(shù)添加新的參數(shù)依賴),我們也不需要在每一處業(yè)務(wù)代碼中添加一個新的參數(shù),只需要在代理類中,對代理的真實(shí)對象進(jìn)行簡單修改即可。
瑕疵
現(xiàn)在我們看到了靜態(tài)代理的優(yōu)勢,但是還有一點(diǎn)需要我們?nèi)ニ伎?,隨著項(xiàng)目中業(yè)務(wù)量的逐漸龐大,真實(shí)對象類的功能可能越來越多:
//接口類
public interface Subject {
void enjoyMusic();
void enjoyFood();
void enjoyBeer();
//...甚至更多
}
//實(shí)現(xiàn)類
public class RealSubject implements Subject {
@Override
public void enjoyMusic() {
System.out.println("enjoyMusic");
}
@Override
public void enjoyFood() {
System.out.println("enjoyFood");
}
@Override
public void enjoyBeer() {
System.out.println("enjoyBeer");
}
//...甚至更多
}
這樣豈不是說明,我們的代理類也要這樣:
public class SubjectProxy implements Subject {
private Subject subject = new RealSubject();
@Override
public void enjoyMusic() {
subject.enjoyMusic();
}
@Override
public void enjoyFood() {
subject.enjoyFood();
}
@Override
public void enjoyBeer() {
subject.enjoyBeer();
}
//...甚至更多
}
靜態(tài)代理的話,確實(shí)如此,隨著真實(shí)對象的功能增多,不可避免的,代理對象的代碼也會隨之臃腫,這是我們不希望看到的,我們更希望的是,即使真實(shí)對象的代碼量再繁重,我們的代理類也不要有太多的改動和臃腫。
動態(tài)代理
直接來看代碼,我們首先聲明一個接口和實(shí)現(xiàn)類:
public interface Subject {
void enjoyMusic();
void enjoyFood();
void enjoyBeer();
}
public class RealSubject implements Subject {
@Override
public void enjoyMusic() {
System.out.println("enjoyMusic");
}
@Override
public void enjoyFood() {
System.out.println("enjoyFood");
}
@Override
public void enjoyBeer() {
System.out.println("enjoyBeer");
}
}
這兩位是老朋友了,接下來我們實(shí)現(xiàn)一個動態(tài)代理類:
public class DynamicProxy implements InvocationHandler {
private Object subject;
public DynamicProxy(Object subject) {
this.subject = subject;
}
public Object bind() {
return Proxy.newProxyInstance(subject.getClass().getClassLoader(),
subject.getClass().getInterfaces(),
this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
method.invoke(subject, args);
return null;
}
}
我們來看業(yè)務(wù)代碼:
@Test
public void dynamicProxy() throws Exception {
RealSubject realSubject = new RealSubject();
Subject proxy = (Subject) new DynamicProxy(realSubject).bind();
System.out.println(proxy.getClass().getName());
proxy.enjoyMusic();
proxy.enjoyFood();
proxy.enjoyBeer();
}
動態(tài)代理中,我們可以看到,最重要的兩個類:
Proxy 和 InvocationHandler
接下來分別對這兩個類的功能進(jìn)行描述.
InvocationHandler接口
我們先看一下Java的API文檔:
InvocationHandler is the interface implemented by the invocation handler of a proxy instance.
Each proxy instance has an associated invocation handler. When a method is invoked on a proxy instance, the method invocation is encoded and dispatched to the invoke method of its invocation handler.
每一個動態(tài)代理類都必須要實(shí)現(xiàn)InvocationHandler這個接口,并且每個代理類的實(shí)例都關(guān)聯(lián)到了一個handler,當(dāng)我們通過代理對象調(diào)用一個方法的時候,這個方法的調(diào)用就會被轉(zhuǎn)發(fā)為由InvocationHandler這個接口的 invoke 方法來進(jìn)行調(diào)用。我們來看看InvocationHandler這個接口的唯一一個方法 invoke 方法:
Object invoke(Object proxy, Method method, Object[] args) throws Throwable
proxy: 指代最終生成的代理對象
method: 指代調(diào)用真實(shí)對象的某個方法的Method對象
args: 指代的是調(diào)用真實(shí)對象某個方法時接受的參數(shù)
我們看到,我們的動態(tài)代理類中,實(shí)現(xiàn)了InvocationHandler接口的invoke方法,這里面三個參數(shù)的作用很簡單,以proxy.enjoyMusic();為例,參數(shù)1表示最終生成的代理對象,參數(shù)2表示enjoyMusic()這個方法對象,參數(shù)3代表調(diào)用enjoyMusic()的參數(shù),此例中是沒有調(diào)用參數(shù)的。
有一個疑問是,這個proxy對象是什么呢,是這個new DynamicProxy(realSubject)嗎?
當(dāng)然不是,我們可以看到,這個代理對象proxy實(shí)際上是調(diào)用bind()方法獲得的,也就是說是通過這個方法獲得的:
Proxy.newProxyInstance(subject.getClass().getClassLoader(),
subject.getClass().getInterfaces(),
this);
Proxy類
先看JavaAPI:
Proxy provides static methods for creating dynamic proxy classes and instances, and it is also the superclass of all dynamic proxy classes created by those methods.
Proxy這個類的作用就是用來動態(tài)創(chuàng)建一個代理對象的類,它提供了許多的方法,但是我們用的最多的就是 newProxyInstance 這個方法:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException
Returns an instance of a proxy class for the specified interfaces that dispatches method invocations to the specified invocation handler.
- loader: 一個ClassLoader對象,定義了由哪個ClassLoader對象來對生成的代理對象進(jìn)行加載
- interfaces: 一個Interface對象的數(shù)組,表示的是我將要給我需要代理的對象提供一組什么接口,如果我提供了一組接口給它,那么這個代理對象就宣稱實(shí)現(xiàn)了該接口(多態(tài)),這樣我就能調(diào)用這組接口中的方法了
- h: 一個InvocationHandler對象,表示的是當(dāng)我這個動態(tài)代理對象在調(diào)用方法的時候,會關(guān)聯(lián)到哪一個InvocationHandler對象上
可以看到,Proxy這個類才會幫助我們生成相應(yīng)的代理類,它是如何知道我們需要生成什么類的代理呢?
看第二個參數(shù),我們把Subject.class作為參數(shù)傳進(jìn)去時,那么我這個代理對象就會實(shí)現(xiàn)了這個接口,這個時候我們當(dāng)然可以將這個代理對象強(qiáng)制類型轉(zhuǎn)化,因?yàn)檫@里的接口是Subject類型,所以就可以將其轉(zhuǎn)化為Subject類型了。
我們看到,我在業(yè)務(wù)代碼中對生成的proxy進(jìn)行了打印:
System.out.println(proxy.getClass().getName());
//結(jié)果:
//$Proxy0
也就是說,通過 Proxy.newProxyInstance 創(chuàng)建的代理對象是在jvm運(yùn)行時動態(tài)生成的一個對象,它并不是我們的InvocationHandler類型,也不是我們定義的那組接口的類型,而是在運(yùn)行是動態(tài)生成的一個對象,并且命名方式都是這樣的形式,以$開頭,proxy為中,最后一個數(shù)字表示對象的標(biāo)號。
到此,動態(tài)代理的基本知識就告一段落了。
參考資料:
java的動態(tài)代理機(jī)制詳解:(對我?guī)椭艽?,衷心感謝!)
http://www.cnblogs.com/xiaoluo501395377/p/3383130.html
知乎:Java 動態(tài)代理作用是什么?
https://www.zhihu.com/question/20794107
本文sample代碼已托管github: