輕鬆理解Java動態代理

我在網上看到很多討論Java動態代理的文章,都寫得挺有水平,但是一般都用了一些高深的詞彙,我個人覺得那些詞彙對初學者來說是有點難理解的,所以,本文章要用很土的語言描述Java動態代理,爭取讓中學生都能聽懂

Java是JDK5中新加的機制,大家都知道Spring是用Java的動態代理實現的,那這個動態代理是什麼東東呢,首先他肯定是個代理,我們先講代理,把代理弄明白了,動態代理就好說了。代理都知道吧,你去買東西就有很多的代理商,他們就是賣原廠的東西。比如,你天天要買肉,豬是農民伯伯養的,但你是從屠夫手上買到肉的,這個屠夫就可以當成是代理。那爲什麼要代理呢,代理有什麼用呢,當然是有事給他做了,對於屠夫這個代理就好理解了,因爲你自己不可能去宰豬吧,所以代理就是去買活豬,然後宰掉再賣給你,當然屠夫有可能給肉注點水,關鍵看他壞不壞,所以屠夫的整個流程就是:


這個流程用代碼怎麼實現呢:我們應該要用三個類You、Butcher、Farmer分別指你、屠夫、農民伯伯。其中農民伯伯又提供一個買肉的方法給屠夫調用,這個方法輸入是錢的數量,返回是肉的數量,都用int型,代碼如下:

class Farmer {
    public int buyMeat(int money) {
        int meat = 0;
        // ... meat = ***;
        return meat;
    }
}

而屠夫則提供一個買肉的方法給你調用,同樣是輸入錢,返回肉,但是會把肉加工一下(殺豬和刮豬毛在代碼中就省了,要不然還得爲豬寫個類),代碼如下:

class Butcher {
    public int buyMeat(int money) {
        Farmer farmer = new Farmer();            // 1.find a farmer.
        int meat = farmer.buyMeat(money);        // 2.buy meat from the farmer.
        meat += 5;                               // 3.inject 5 pound water into the meat, so weight will increase. 
        return meat;                             // 4.return to you.
    }
}
然你從屠夫手上買肉的代碼就變成這樣:
class You {
    public void work() {
        int youMoney = 10;
        Butcher butcher = new Butcher();    				// find a butcher.
        int meat = butcher.buyMeat(youMoney);
        System.out.println("Cook the meat, weight: " + meat);		// you cooked it.	
    }
}

這個程序我們還可以優化一下,我們發現屠夫有農民有一個相同的買肉方法,我們可以提取一個接口,叫爲商販(pedlar)吧,以後你買肉就不用管他是屠夫還是農民伯伯了,只要他有肉賣就可以了,我們提取一個接口後,代碼就變成這樣:

class You {
    public void work() {
        int youMoney = 10;
        Peldar peldar= new Butcher();                               // find a peldar.
        int meat = peldar.buyMeat(youMoney);
        System.out.println("Cook the meat, weight: " + meat);        // you cooked it.    
    }
}
interface Peldar {
	int buyMeat(int money);
}
class Butcher implements Peldar {
    @Override
    public int buyMeat(int money) {
        Farmer farmer = new Farmer();            // 1.find a farmer.
        int meat = farmer.buyMeat(money);        // 2.buy meat from the farmer.
        meat += 5;                               // 3.inject 5 pound water into the meat, so weight will increase. 
        return meat;                             // 4.return to you.
    }
}

class Farmer implements Peldar {
    @Override
    public int buyMeat(int money) {
        int meat = 0;
        // ... meat = ***;
        return meat;
    }
}

這就是代理,值得注意的是一般代理類和最終類會實現同一接口,這樣的好處是,調用者就不用關心當前引用的到底是代理還是最終類。

不過這叫靜態代理,因爲代理類(屠夫類)是你親手寫,動態代理就是Java在運行的時候,動態生成一個等價的代理類。雖然類是動態生成的,但是殺豬和注水的代碼還是要寫的,只是不要寫一個類了。寫到哪裏呢,寫到下面這個接口裏面:

public interface InvocationHandler { 
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable; 
}
參數是什麼意思呢,我寫成這樣,可能你就明白了:
public interface InvocationHandler { 
    public Object invoke(Object butcher, Method buyMeat, Object[] money) throws Throwable; 
}
第一個參數是自動生成的代理類的一個對象(自動生成的屠夫類的對象),第二個參數是正前正在被調用的方法的對象(方法怎麼還有對象呢,參見Java反射機制),我們這裏只有一個方法叫buyMeat,所以這個參數代表的肯定就是它了,第三個參數是傳給前面那個方法的參數數組,buyMeat只有一個參數,所以這個數組只會有一個元素。於是殺豬注水的代碼寫進來就變成這樣了:
InvocationHandler mInvocationHandler = new InvocationHandler() {		
    @Override
    public Object invoke(Object butcher, Method buyMeat, Object[] args) throws Throwable {
        Farmer farmer = new Farmer();            		// 1.find a farmer.
        int meat = (Integer) buyMeat.invoke(farmer, args);      // 2.buy meat from the farmer.
        meat += 5;                               		// 3.inject 5 pound water into the meat, so weight will increase. 
        return meat;                             		// 4.return to you.
    }
};
這個裏調用農民伯伯的買肉方法有點不符常規,這裏是反射機制調用法,意思是這樣的,以farmer對象爲接受者來調用buyMeat方法,跟直接調用farmer的方法是一樣的,你可能會問那爲什麼不直接調用呢,你可能沒注意,invoke的第一個參數類型是Object,所以你可以向任何對象發佈調用命令(但不一定會成功,什麼時候會成功等下說),如果你有很多farmer對象,甚至不是farmer對象,只要某接口的實例就可以(哪個接口等下說明,我們先命名爲A接口),就可以當成參數傳進來,然後對其進行方法調用。現在我們來看看如何生成代理類吧,很簡單,可以調用Proxy的工廠方法,如下:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)  
    throws IllegalArgumentException
解釋參數,第一個ClassLoader是用來加載代理類的(關於ClassLoader,本文暫不講解),你暫不瞭解也沒關係,第二個是一個數組,每個元數都是一個接口,新生成的代理都會實現所有這些接口,傳給InvocationHandler.invoke第二個參數的方法,必定屬於所有這些接口中的方法,上一段落說的那個A接口必須是數組中的一個元素,上一段落說的那個調用成失敗問題也明瞭了。第三個參數InvocationHandler更好理解了,就是隻要代理類中的任何方法被調用,就會通知這個InvocationHandler。下面寫出完整代碼:
class You {
    public void work() {
        int youMoney = 10;
        
        Peldar peldarProxy = (Peldar) Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{Peldar.class}, mInvocationHandler);
        int meat = peldarProxy.buyMeat(youMoney);
        
        System.out.println("Cook the meat, weight: " + meat);    
    }
    
    InvocationHandler mInvocationHandler = new InvocationHandler() {        
        @Override
        public Object invoke(Object butcher, Method buyMeat, Object[] args)
                throws Throwable {
            Farmer farmer = new Farmer();                           // 1.find a farmer.
            int meat = (Integer) buyMeat.invoke(farmer, args);      // 2.buy meat from the farmer.
            meat += 5;                                              // 3.inject 5 pound water into the meat, so weight will increase. 
            return meat;                                            // 4.return to you.
        }
    };
    
}
interface Peldar {
    int buyMeat(int money);
}

class Farmer implements Peldar {
    @Override
    public int buyMeat(int money) {
        int meat = 0;
        // ... meat = ***;
        return meat;
    }
}
這裏You類裏生成一個代理類,在代理類的buyMeat被調用時,代碼就跟之前的靜態代理一樣的了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章