引入
最近學習javaweb視頻時,學習到了一種新的設計模式叫動態代理,對於這種設計模式我在這裏做一下簡單的總結,以免以後忘記了。
在說動態代理的時候我們需要先說一說,裝飾設計模式,裝飾設計模式具有增強功能的作用,下面我先簡單的舉個例子來說說什麼時裝飾設計模式。
首先,我們先假設一個場景,以後有了無人駕駛(雖然這個東西好像現在就有了),這個時候,我們需要用java來開發這個項目,於是呢?sun公司就提供了一種接口叫做car,然後其他大公司就根據這個接口來創建自己的類,但是呢?比如說,谷歌把這個無人駕駛的汽車造出來了,叫做GoogleCar,並且對外開放出售方式,但不提供源碼。某一天呢?有一家公司買了谷歌公司的這個產品,需要對某些功能進行增強,但是呢,他們又不知道這個東西的源碼是怎麼實現的,那麼我們該如何去處理呢?
//sun公司提供的對外接口
public interface Car {
public void start();
public void run();
public void stop();
}
//谷歌實現的接口
public class GoogleCar implements Car {
@Override
public void start() {
System.out.println("無人汽車準備啓動");
}
@Override
public void run() {
System.out.println("無人汽車正常運行");
}
@Override
public void stop() {
System.out.println("無人汽車停止運行");
}
}
首先,我們先想到的肯定是 繼承 這種方式,因爲這種方式是處理當前狀況最爲簡單的一種處理方式了。
public class MyCar extends GoogleCar{
@Override
public void start() {
//需要增強的功能
System.out.println("天氣是否良好?");
System.out.println("路況是否擁堵?");
super.start();
}
@Override
public void run() {
super.run();
}
@Override
public void stop() {
super.stop();
}
}
以上便是用繼承的方式去處理這個項目,我們大致一看感覺沒什麼問題,可以說是完美解決了這個問題,但是這樣做卻存在一個安全隱患,相關例子如下:
public class One {
public void test01() {
System.out.println("One:test01");
test02();
}
public void test02() {
System.out.println("One:test02");
}
}
public class Two extends One{
@Override
public void test01() {
super.test01();
}
@Override
public void test02() {
// super.test02();
}
public static void main(String[] args) {
Two two = new Two();
two.test01();
}
}
以上有兩個類:一個是One,另一個是Two繼承One而來,我說的安全隱患就是,如果我們不小心註釋了一個// super.test02(); 那麼不就會產生這種安全隱患了嗎?所以這種有隱患的情況產生,所以Google肯定會將其GoogleCar進行小小改變如下:
(加上final,禁止繼承,所以我們只能使用裝飾設計模式)
final public class GoogleCar implements Car {
@Override
public void start() {
System.out.println("無人汽車準備啓動");
}
@Override
public void run() {
System.out.println("無人汽車正常運行");
}
@Override
public void stop() {
System.out.println("無人汽車停止運行");
}
}
所以這個時候我們只能使用裝飾設計模式:
public class MyCar implements Car {
private Car car;
public MyCar(Car car) {
this.car = car;
}
@Override
public void start() {
System.out.println("天氣是否良好?");
System.out.println("路況是否擁堵?");
car.start();
}
@Override
public void run() {
car.run();
}
@Override
public void stop() {
car.stop();
}
}
裝飾設計模式比繼承好在哪?
首先,我們先設想這樣一個場景,有一家飲品店,這家店呢,目前只賣,咖啡,奶茶,啤酒,這三中商品。但是呢他們都是飲料,他們都有一些共同的屬性,就是喝,所以就有如下代碼:
public interface Drinks {
public void drink();
}
public class Coffee implements Drinks{
@Override
public void drink() {
System.out.println("喝咖啡");
}
}
public class TeaWithMilk implements Drinks {
@Override
public void drink() {
System.out.println("喝奶茶");
}
}
public class Beer implements Drinks {
@Override
public void drink() {
System.out.println("喝啤酒");
}
}
然後呢?某一天呢?有人想喝加糖的咖啡,於是呢?我就在創建一個類來繼承這個Coffee這個類,又有人想喝加糖的奶茶,這個時候又加一個類,再後來呢?居然有人想喝加糖的啤酒,所以在加上一個類,就這樣如此下去,那麼我們這個體系將會越來越臃腫,顯得特別不夠靈活。
所以這個時候我們使用裝飾設計模式來處理這個問題,但是這個時候我們又產生了一個問題,我們就不能直接創建一個方法來處理這個問題嗎?也可以我們創建一個方法就行呢,來了什麼就加糖,但是這樣會又一個問題,什麼問題呢?在這裏我們的Drinks這個類只有一個drink()方法,但如果我們有很多個,每個方法都需要增強,是不是又要創建很多個方法來處理呢,那爲什麼,就不把這些方法封裝到一個類裏面呢,一起處理了,這個類不就變成了裝飾設計模式嗎?
裝飾模式比繼承要靈活。避免了繼承體系臃腫。
而且降低了類於類之間的關係。
走心的豬:淺析面向對象之——裝飾者模式之於繼承機制的存在價值與意義
裝飾設計模式的弊端—>動態代理
當一個接口裏面的方法有200多個方發時,我們只對一些方法進行加強,那麼我們如果還是使用裝飾設計模式的化,那麼會把人搞死的。所以我們有了,動態代理。
我們是用那個谷歌汽車那個例子進行說明.
(在這裏呢,我只能寫寫動態代理的代碼實現,是如何實現的)
public interface ICar {
public String start(int a,int b) ;
public void run();
public void stop();
}
public class GoogleCar implements ICar {
@Override
public String start(int a,int b) {
System.out.println("谷歌汽車開始啓動");
return "start___"+a+"___"+b;
}
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("谷歌汽車以在路上運行");
}
@Override
public void stop() {
// TODO Auto-generated method stub
System.out.println("谷歌汽車運行結束");
}
}
public class Test {
public static void main(String[] args) {
//1parm:固定值:告訴虛擬機用哪個字節碼加載器加載內存中創建出的字節碼文件
//2parm:告訴虛擬機內存中正在被創建的字節碼文件中應該有哪些方法
//3parm:告訴虛擬機正在被創建的字節碼上的各種方法如何處理
ICar car = (ICar)Proxy.newProxyInstance(Test.class.getClassLoader(), GoogleCar.class.getInterfaces(), new InvocationHandler() {
//method:代表正在執行的方法
//args:代表正在執行的方法中的參數
//Object:代表方法執行完畢之後的返回值
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object obj = null;
if(method.getName().equals("start")) {
System.out.println("檢查天氣是否良好");
//這個方法是反射機制裏面的method.invoke(new GoogleCar(),args);
obj = method.invoke(new GoogleCar(),args);
System.out.println("檢查路況是否擁擠");
}else {
obj = method.invoke(new GoogleCar(),args);
}
return obj;
}
});
car.start(1,4);
car.run();
car.stop();
}
}
最後呢,來說說動態代理的大致過程,
這個是我的class文件
在這裏呢?我們都知道一個道理,一個class文件就代表一個.java文件,然而這裏卻多了一個東西,
Test$1.class,其實我想說的是這個東西就是動態代理創建出來的。
好了我來說說,動態代理的大致實現過程,首先呢,我們需要一個類加載器,來加載我們的字節碼文件,在
這裏呢,我們的類加載器是Test.class.getClassLoader(),然後呢我們就加載了GoogleCar.class這個文件,然
後這個文件就進入了內存當中,然後呢我們用
GoogleCar.class.getInterfaces(),就知道了這個類中實現的接口上有多少個方法,然後便是我們的 new
InvocationHandler(){}這個便是動態代理的關鍵部分,在裏面呢?有這樣一段代碼:
if(method.getName().equals("start")) {
System.out.println("檢查天氣是否良好");
obj = method.invoke(new GoogleCar(),args);
System.out.println("檢查路況是否擁擠");
}else {
obj = method.invoke(new GoogleCar(),args);
}
這個是我用notepad打開GoogleCar.class的內容,這個對於我們來說是看不懂的,但是對於計算機而言它卻
非常熟悉這個東西,就開始判斷,如果發現方法是start就把他加強後,然後寫入到Test$1.class這個文件中,
這個便是動態代理自動生成的一個文件,如果不是start方法,那就跟簡單了,直接把那個方法編譯好的字節
碼,直接寫入到Test$1.class這個文件中去,等處理完了之後,就直接調用就可以。
以下便是Test$1.class文件的部分截圖