[jvm解析系列][十四]動態代理和裝飾模式,帶你看原始碼深入理解裝飾模式和動態代理的區別。
阿新 • • 發佈:2018-12-23
不知道大家知不知道設計模式中有一種叫做裝飾,舉一個簡單的例子。
一天一個年輕領導小王講話:咳咳,我們一定要xxx抓緊xxxx學習xxx的精神!好,今天的會議結束!
然後有一個老領導李同志接過來說:那個我在補充兩點,個別同志xxx,一定要注意xxx。好散會。
然後另一天小王同志又在講話:xxx兩手都要抓,xxxx一定要注意。
這個時候老周同志出來了:嗯,小王講的很好,我還有幾點要補充xxxx。
那麼很明顯,小王同志的講話方法不是很讓人滿意,那麼老李領導或者老周領導可以接過來繼續裝修一下。其實這就是裝飾模式。我們畫張圖來理一下關係。
我們根據圖來寫一下程式碼
在呼叫老李補充的時候就會輸出:public class DecoratorDemo { public static void main(String[] args){ new 老李(new 小王()).講話(); } } interface 開會{ public void 講話(); }; class 小王 implements 開會{ public void 講話(){ System.out.println("小王同志在講話"); }; } class 補充講話{ 開會 meeting; public 補充講話(開會 meeting){ this.meeting = meeting; } public void 講話(){ meeting.講話(); } } class 老李 extends 補充講話{ public 老李(開會 meeting) { super(meeting); // TODO Auto-generated constructor stub } public void 講話(){ super.講話(); System.out.println("老李同志補充講話"); }; } class 老周 extends 補充講話{ public 老周(開會 meeting) { super(meeting); // TODO Auto-generated constructor stub } public void 講話(){ super.講話(); System.out.println("老周同志補充講話"); }; }
小王同志在講話
老李同志補充講話
但是這種方法雖然做到了補充功能但是整個過程都是靜態編譯好的。在執行時期也很難修改。
我們接下來看一看動態代理:
同樣的例子,我們來實現一下程式碼。
輸出:public class DynamicProxyDemo { public static void main(String[] args) { // TODO Auto-generated method stub 開會 meeting = (開會) new 代理().bind(new 小王()); meeting.講話(); } } interface 開會{ public void 講話(); } class 小王 implements 開會{ public void 講話(){ System.out.println("小王同志在講話"); }; } class 代理 implements InvocationHandler{ Object obj ; public Object bind(Object obj) { this.obj = obj; return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), this); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // TODO Auto-generated method stub System.out.println("我要補充兩句。。。"); return method.invoke(obj, args); } }
我要補充兩句。。。
小王同志在講話
顯而易見的是動態代理實現了動態的程式設計,在原始類和介面都沒有知道的時候就可以確定代理類需要做什麼。比如說我們把小王講話換成小張跳舞。其他的都不用變,最後結果還是一樣的。
程式碼
輸出:public class DynamicProxyDemo { public static void main(String[] args) { // TODO Auto-generated method stub 舞廳 meeting = (舞廳) new 代理().bind(new 小張()); meeting.跳舞(); } } interface 舞廳{ public void 跳舞(); } class 小張 implements 舞廳{ public void 跳舞(){ System.out.println("小張同志在跳舞"); }; } class 代理 implements InvocationHandler{ Object obj ; public Object bind(Object obj) { this.obj = obj; return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), this); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // TODO Auto-generated method stub System.out.println("我要補充兩句。。。"); return method.invoke(obj, args); } }
<p class="p1">我要補充兩句。。。</p><p class="p1">小張同志在跳舞</p>
可以看出來這位老領導興致勃勃,怎麼都要講兩句,也就是說裝飾模式真的是在靜態的狀態上補充和加強原來的程式碼。但是動態代理更像是一個惡霸,不管你幹什麼只要過來我這一定要輸出我想輸出的東西。
那麼他是怎麼實現的呢?
看我們動態代理的原始碼。很有意思我們唯一疑惑的地方應該是他:
return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), this);
他到底返回了什麼?我們跟進去原始碼看看:
我把幾個比較重要的步驟擷取下來:
final Class<?>[] intfs = interfaces.clone();//得到介面
Class<?> cl = getProxyClass0(loader, intfs);//生成一個代理類
final Constructor<?> cons = cl.getConstructor(constructorParams);//得到代理類構造器
return cons.newInstance(new Object[]{h});//返回一個代理類例項
我們可以通過上面的程式碼看出來,他根據我們傳入的類的資訊生成了一個代理類。其實在這個過程中他根據需要的介面資訊生成了一個類然後寫入了代理的方法,在每一個方法裡面都使用了invoke的呼叫,最後通過這一句話呼叫到了我們真正想要修飾的方法。
return method.invoke(obj, args);
(由於我的電腦最近新裝的系統,沒有反編譯裝置,沒有辦法看輸出的類,所以通過文字敘述和圖片描述)
圖片: