動(dòng)機(jī)
學(xué)習(xí)動(dòng)機(jī)來源于RxCache,在研究這個(gè)庫的源碼時(shí)么抗,被這個(gè)庫的設(shè)計(jì)思路吸引了干奢,該庫的原理就是通過動(dòng)態(tài)代理和Dagger的依賴注入,實(shí)現(xiàn)Android移動(dòng)端Retrofit的緩存功能庐舟。
既然在項(xiàng)目中嘗試使用這個(gè)庫猪半,當(dāng)然要從設(shè)計(jì)的角度思考作者的思路,動(dòng)態(tài)代理必然涉及到Java的反射在旱,既然是反射摇零,性能當(dāng)然會(huì)有所降低,那么是否有更好的思路呢桶蝎,使用動(dòng)態(tài)代理的優(yōu)勢有哪些驻仅?
關(guān)于動(dòng)態(tài)代理,百度上面的資料數(shù)不勝數(shù)登渣,今天也借鑒其他前輩的學(xué)習(xí)總結(jié)噪服,自己實(shí)踐一次代理的實(shí)現(xiàn)。
靜態(tài)代理
代理分為動(dòng)態(tài)代理和靜態(tài)代理胜茧,我們先看靜態(tài)代理的代碼:
我們首先定義一個(gè)接口:
public interface Subject {
void enjoyMusic();
}
我們接下來實(shí)現(xiàn)一個(gè)Subject的實(shí)現(xiàn)類:
public class RealSubject implements Subject {
@Override
public void enjoyMusic() {
System.out.println("enjoyMusic");
}
}
在不考慮代理模式的情況下粘优,我們調(diào)用Subject的真實(shí)對(duì)象,我們代碼中必然是這樣:
@Test
public void testNoProxy() throws Exception {
Subject subject= new RealSubject();
subject.enjoyMusic();
}
上面是我們的業(yè)務(wù)代碼呻顽,我們這樣使用當(dāng)然沒有問題雹顺,但是我們需要考慮的一點(diǎn)是,如果我們的業(yè)務(wù)代碼中多次引用了這個(gè)類廊遍,并且在之后的版本迭代中嬉愧,我們需要修改(或者替換)這個(gè)類,我們需要在引用這個(gè)對(duì)象的代碼處進(jìn)行修改——也就是說我們需要修改業(yè)務(wù)代碼喉前。
這顯然不是良好的設(shè)計(jì)没酣,我們希望業(yè)務(wù)代碼不需要修改的前提下,進(jìn)行RealSubject的修改(或者替換)卵迂,這時(shí)我們可以通過代理模式裕便,創(chuàng)建一個(gè)代理類,從而達(dá)到控制RealSubject對(duì)象的引用 :
public class SubjectProxy implements Subject {
private Subject subject = new RealSubject();
@Override
public void enjoyMusic() {
subject.enjoyMusic();
}
}
我們在業(yè)務(wù)代碼中通過代理類狭握,達(dá)到調(diào)用真實(shí)對(duì)象RealSubject的對(duì)應(yīng)方法:
@Test
public void staticProxy() throws Exception {
SubjectProxy proxy = new SubjectProxy();
proxy.enjoyMusic();
}
這就是靜態(tài)代理闪金,優(yōu)勢是顯然的,如果我們需要一個(gè)新的對(duì)象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ù)代碼中添加一個(gè)新的參數(shù),只需要在代理類中今妄,對(duì)代理的真實(shí)對(duì)象進(jìn)行簡單修改即可郑口。
瑕疵
現(xiàn)在我們看到了靜態(tài)代理的優(yōu)勢鸳碧,但是還有一點(diǎn)需要我們?nèi)ニ伎迹S著項(xiàng)目中業(yè)務(wù)量的逐漸龐大犬性,真實(shí)對(duì)象類的功能可能越來越多:
//接口類
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í)對(duì)象的功能增多套利,不可避免的,代理對(duì)象的代碼也會(huì)隨之臃腫鹤耍,這是我們不希望看到的肉迫,我們更希望的是,即使真實(shí)對(duì)象的代碼量再繁重稿黄,我們的代理類也不要有太多的改動(dòng)和臃腫喊衫。
動(dòng)態(tài)代理
直接來看代碼,我們首先聲明一個(gè)接口和實(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)一個(gè)動(dòng)態(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();
}
動(dòng)態(tài)代理中,我們可以看到旁瘫,最重要的兩個(gè)類:
Proxy 和 InvocationHandler
接下來分別對(duì)這兩個(gè)類的功能進(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.
每一個(gè)動(dòng)態(tài)代理類都必須要實(shí)現(xiàn)InvocationHandler這個(gè)接口,并且每個(gè)代理類的實(shí)例都關(guān)聯(lián)到了一個(gè)handler联四,當(dāng)我們通過代理對(duì)象調(diào)用一個(gè)方法的時(shí)候,這個(gè)方法的調(diào)用就會(huì)被轉(zhuǎn)發(fā)為由InvocationHandler這個(gè)接口的 invoke 方法來進(jìn)行調(diào)用撑教。我們來看看InvocationHandler這個(gè)接口的唯一一個(gè)方法 invoke 方法:
Object invoke(Object proxy, Method method, Object[] args) throws Throwable
proxy: 指代最終生成的代理對(duì)象
method: 指代調(diào)用真實(shí)對(duì)象的某個(gè)方法的Method對(duì)象
args: 指代的是調(diào)用真實(shí)對(duì)象某個(gè)方法時(shí)接受的參數(shù)
我們看到朝墩,我們的動(dòng)態(tài)代理類中,實(shí)現(xiàn)了InvocationHandler接口的invoke方法伟姐,這里面三個(gè)參數(shù)的作用很簡單收苏,以proxy.enjoyMusic();為例,參數(shù)1表示最終生成的代理對(duì)象愤兵,參數(shù)2表示enjoyMusic()這個(gè)方法對(duì)象鹿霸,參數(shù)3代表調(diào)用enjoyMusic()的參數(shù),此例中是沒有調(diào)用參數(shù)的秆乳。
有一個(gè)疑問是懦鼠,這個(gè)proxy對(duì)象是什么呢,是這個(gè)new DynamicProxy(realSubject)嗎屹堰?
當(dāng)然不是肛冶,我們可以看到,這個(gè)代理對(duì)象proxy實(shí)際上是調(diào)用bind()方法獲得的扯键,也就是說是通過這個(gè)方法獲得的:
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這個(gè)類的作用就是用來動(dòng)態(tài)創(chuàng)建一個(gè)代理對(duì)象的類睦袖,它提供了許多的方法,但是我們用的最多的就是 newProxyInstance 這個(gè)方法:
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: 一個(gè)ClassLoader對(duì)象荣刑,定義了由哪個(gè)ClassLoader對(duì)象來對(duì)生成的代理對(duì)象進(jìn)行加載
- interfaces: 一個(gè)Interface對(duì)象的數(shù)組馅笙,表示的是我將要給我需要代理的對(duì)象提供一組什么接口伦乔,如果我提供了一組接口給它,那么這個(gè)代理對(duì)象就宣稱實(shí)現(xiàn)了該接口(多態(tài))董习,這樣我就能調(diào)用這組接口中的方法了
- h: 一個(gè)InvocationHandler對(duì)象烈和,表示的是當(dāng)我這個(gè)動(dòng)態(tài)代理對(duì)象在調(diào)用方法的時(shí)候,會(huì)關(guān)聯(lián)到哪一個(gè)InvocationHandler對(duì)象上
可以看到皿淋,Proxy這個(gè)類才會(huì)幫助我們生成相應(yīng)的代理類斥杜,它是如何知道我們需要生成什么類的代理呢?
看第二個(gè)參數(shù)沥匈,我們把Subject.class作為參數(shù)傳進(jìn)去時(shí),那么我這個(gè)代理對(duì)象就會(huì)實(shí)現(xiàn)了這個(gè)接口忘渔,這個(gè)時(shí)候我們當(dāng)然可以將這個(gè)代理對(duì)象強(qiáng)制類型轉(zhuǎn)化高帖,因?yàn)檫@里的接口是Subject類型,所以就可以將其轉(zhuǎn)化為Subject類型了畦粮。
我們看到散址,我在業(yè)務(wù)代碼中對(duì)生成的proxy進(jìn)行了打印:
System.out.println(proxy.getClass().getName());
//結(jié)果:
//$Proxy0
也就是說宣赔,通過 Proxy.newProxyInstance 創(chuàng)建的代理對(duì)象是在jvm運(yùn)行時(shí)動(dòng)態(tài)生成的一個(gè)對(duì)象预麸,它并不是我們的InvocationHandler類型,也不是我們定義的那組接口的類型儒将,而是在運(yùn)行是動(dòng)態(tài)生成的一個(gè)對(duì)象吏祸,并且命名方式都是這樣的形式,以$開頭钩蚊,proxy為中贡翘,最后一個(gè)數(shù)字表示對(duì)象的標(biāo)號(hào)。
到此砰逻,動(dòng)態(tài)代理的基本知識(shí)就告一段落了鸣驱。
參考資料:
java的動(dòng)態(tài)代理機(jī)制詳解:(對(duì)我?guī)椭艽螅孕母兄x蝠咆!)
http://www.cnblogs.com/xiaoluo501395377/p/3383130.html
知乎:Java 動(dòng)態(tài)代理作用是什么踊东?
https://www.zhihu.com/question/20794107
本文sample代碼已托管github: