- 近來在研究Retrofit的源碼拾徙,發(fā)現(xiàn)使用了動態(tài)代理的方式笙什;發(fā)現(xiàn)自己一直以來都是對這個方式一知半解飘哨,這次想要徹底的弄明白。
靜態(tài)代理
要想了解動態(tài)代理琐凭,首先要知道靜態(tài)代理芽隆,網(wǎng)上也有很多相關(guān)的文章,無非是說明相關(guān)的代碼怎么寫统屈,等等胚吁,看完好像是明白的,但是到了自己使用的時候你會感覺到其實(shí)自己還是不怎么懂愁憔;
那下面開始說一下靜態(tài)代理:
-
先看一下靜態(tài)代理的類圖:
image.png - 從類圖我們可以看到 SubjectProxy(代理類)需要代理目標(biāo)類(SubjectImp)腕扶,然后調(diào)目標(biāo)類中的方法;
- SubjectProxy(代理類)必須持有目標(biāo)類(SubjectImp)的引用;
- 外層首先調(diào)SubjectProxy類的callMethod()方法吨掌,然后在callMethod()方法內(nèi)調(diào)用目標(biāo)類(SubjectImp)的callMethod()方法半抱;
- 因此不僅需要新建一個目標(biāo)類對象脓恕,同時還需要新建一個代理類對象;
代碼實(shí)例
嗯窿侈,看起來好像有點(diǎn)啰嗦进肯,那就代碼說話吧;
- 假設(shè)現(xiàn)在有個學(xué)生接口棉磨,需要做作業(yè)和上英語課
public interface IStudent {
void doHomework();
void learnEnglish();
}
- 我們來定義一個中學(xué)生類江掩,那么中學(xué)的學(xué)生是怎么做的呢?
public class MiddleSchoolStudent implements IStudent {
@Override
public void doHomework() {
System.out.println("做中學(xué)作業(yè)");
}
@Override
public void learnEnglish() {
System.out.println("上中學(xué)的英語課");
}
}
現(xiàn)在有一個需求乘瓤,需要計算做中學(xué)作業(yè)以及上英語課所花費(fèi)的時間是多少环形?
- 那么你可以選擇直接在MiddleSchoolStudent 類種修改,但是可以能存在小學(xué)生類衙傀、大學(xué)生類抬吟,如果直接在類中修改,那么就需要修改很多地方统抬,就破壞了閉合原則火本;
- 那應(yīng)該怎么做呢,可以使用使用一個時間計算的代理類聪建,還記得上面類圖嗎钙畔,代理類需要實(shí)現(xiàn)需要代理的接口;
public class TimeProxy implements IStudent {
private final IStudent student;
public TimeProxy(IStudent student) {
this.student = student;
}
@Override
public void doHomework() {
TimeUtil.start("doHomework");
student.doHomework();
TimeUtil.finish("doHomework");
}
@Override
public void learnEnglish() {
student.learnEnglish();
}
}
- 然后通過代理類來做計算:
public static void main(String[] args) {
IStudent midStu = new MiddleSchoolStudent();
TimeProxy timeProxy = new TimeProxy(midStu);
timeProxy.doHomework();
}
動態(tài)代理
- 現(xiàn)在靜態(tài)代理講清楚了金麸,那下面來看看動態(tài)代理擎析,讓我們代碼先行。
代碼實(shí)例
使用的Java提供的Proxy類來創(chuàng)建動態(tài)代理挥下;
- 首先需要實(shí)現(xiàn)InvocationHandler類揍魂,為什么不叫InvocationProxy呢?難道這個類不是代理類嗎棚瘟?
static class Invocation implements InvocationHandler {
private final IStudent student;
public Invocation(IStudent student) {
this.student = student;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
TimeUtil.start(method.getName());
Object result = method.invoke(student, args);
TimeUtil.finish(method.getName());
return result;
}
}
- 然后我們在Main函數(shù)中調(diào)用方法Proxy來創(chuàng)建動態(tài)代理;
public static void main(String[] args) {
IStudent midStu = new MiddleSchoolStudent();
IStudent proxy = (IStudent) Proxy.newProxyInstance(
IStudent.class.getClassLoader(),
new Class[]{IStudent.class},
new MidStudentProxy(midStu));
proxy.doHomework();
}
- 看到這里大家已經(jīng)有很多疑惑现斋,Proxy內(nèi)部是怎么做到的呢?為什么要實(shí)現(xiàn)InvocationHandler類偎蘸?
斷點(diǎn)追蹤
- 下面讓我們通過斷點(diǎn)的方式來查詢Proxy.newProxyInstance究竟做了什么工作庄蹋?
-
首先我們點(diǎn)擊進(jìn)入newProxyInstance函數(shù),在內(nèi)部添加一個斷點(diǎn)禀苦,然后使用debug模式運(yùn)行蔓肯;
newProxyInstance.png
image.png -
跟蹤下去,我們可以發(fā)現(xiàn)通過getProxyClass0() 方法振乏,產(chǎn)生了一個$Proxy0類蔗包;
Proxy0.png
- 這個$Proxy0是什么東西呢?
- 可以看到慧邮,這里獲取了$Proxy0類的構(gòu)造器调限,并且傳入的參數(shù)為InvocationHandler對象舟陆;
-
在代碼最后調(diào)用了newInstance實(shí)例化了一個$Proxy0對象,h為我們創(chuàng)建的Invocation類耻矮;
image.png
-
讓我們繼續(xù)追蹤秦躯,proxy.doHomework()方法的調(diào)用過程
image.png
- 可以發(fā)現(xiàn) proxy 是系統(tǒng)通過Proxy.newProxyInstance() 方法 生成的一個新的對象
Proxy0類實(shí)現(xiàn)了IStudent接口裆装,包裹了一個我們傳進(jìn)去的Invocation對象踱承,而Invocation對象又持有了一個目標(biāo)類的引用;
- 因此哨免,真正的代理類并不是Invocation茎活,而是$Proxy0這個系統(tǒng)生成的代理類;
$Proxy0 是如何產(chǎn)生的琢唾?
-
那現(xiàn)在回到我們之前產(chǎn)生的問題载荔,$Proxy0這個類是如何生成的呢?
- 其實(shí)上面已經(jīng)分析到了采桃,是通過 getProxyClass0() 這個方法來生成的懒熙,下面我們一起來看看這個方法。
- 查看源碼發(fā)現(xiàn)Proxy類內(nèi)部有一個ProxyClassFactory類普办。
- ProxyClassFactory.png
- 繼續(xù)跟蹤工扎,忽略我們并不關(guān)心的功能,發(fā)現(xiàn)ProxyClassFactory內(nèi)部有一個方法泌豆,看注釋很明顯就是生成代理類操作,ProxyGenerator.generateProxyClass();
- generateProxyClass.png
下面我們來驗(yàn)證一下定庵,ProxyGenerator.generateProxyClass () 這個方法到底是不是如我們所猜想的,是通過它來生成代理類的呢踪危?
- 我們可以把按照這個方法來生成對應(yīng)的代理類;
public static void main(String[] args) {
IStudent midStu = new MiddleSchoolStudent();
IStudent proxy = (IStudent) Proxy.newProxyInstance(
IStudent.class.getClassLoader(),
new Class[]{IStudent.class},
new Invocation(midStu));
proxy.doHomework();
proxy.learnEnglish();
byte[] bytes = ProxyGenerator.generateProxyClass(proxy.getClass().getSimpleName(), proxy.getClass().getInterfaces());
try {
// 保存到proxy.class 文件中
FileOutputStream fileOutputStream = new FileOutputStream(new File("out/proxy.class"));
fileOutputStream.write(bytes);
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
- 打開proxy.class文件猪落,讓我們來見識一下通過ProxyGenerator.generateProxyClass()生成的代理類的廬山真面目:
- proxy.class.png
- 可以看到贞远,這個代理類實(shí)現(xiàn)了我們定義的IStudent接口;
總結(jié)
- 也就是說笨忌,當(dāng)我們調(diào)用Proxy.newProxyInstance的時候蓝仲,它會根據(jù)傳遞的接口,來聲明對應(yīng)接口的代理類 $Proxy0 官疲;
- 我們在上面的分析已經(jīng)知道袱结,h 是創(chuàng)建$Proxy0 傳遞的Invocation類,可以看到在各個方法內(nèi)部都調(diào)用了invoke方法途凫;那如何調(diào)用invoke這就可以解釋的通了垢夹;
- 調(diào)
Proxy0代理類對象的doHomework()方法维费,此方法內(nèi)部持有Invocation對象果元,那實(shí)際是調(diào)用Invocation對象的invoke() 方法促王,然后再調(diào)用 Invocation內(nèi)部目標(biāo)類對象的learnEnglish() 方法;