先不說話,直接來張姐姐照片,稱呼飛姐即可,希望飛姐能帶飛,
原創不易,麻煩先三連,再細看,謝謝
示例的原始碼可以直接通過csdn下載也可以通過git匯出:https://github.com/igdnss/java_proxy.git
java代理模式
- 定義
- 靜態代理
- 動態代理
我:飛姐,最近一直在看一些框架的原始碼,發現到很多地方都用到了代理,尤其是動態代理,例如SpringData jpa中EntityManager的創建程序,
飛姐:是的啊,代理模式是很常見的一種設計模式,一定要學會哦!
我:嗯嗯,我個人感覺在23種設計模式中代理模式比較難,尤其是動態代理,
飛姐:當然,學代理模式一定要看原始碼,不然物件怎么創建出來的你都搞不清楚,
我:這樣啊,難怪我掌握的不牢,
飛姐:嗯,代理模式深究的話還需要追述到jvm,走,飛姐帶你去探究一下,爭取能一次性搞懂,
定義
我:飛姐,你能簡單介紹一下什么時候代理模式嗎?
飛姐:好啊,其實不難理解,
所謂的代理,就是中介,當我們想使用某個物件的時候,我們不是直接使用,而是通過中介(代理)來與之取得聯系,說的專業一點就是給目標物件提供一個代理物件,并由這個代理物件去控制對目標物件的參考,
我:嗯,解釋到位,是不難理解,那飛姐知道干嘛要引入這個代理嗎?不參考不可以嗎,直接與目標物件取得聯系不就可以嗎?
飛姐:你就別考我啦,難不到我的呢,使用代理呢我覺得最起碼有兩個好處:
- 通過代理物件訪問目標物件可以有效防止直接訪問目標物件給系統帶來的不必要復雜性,
- 通過代理物件擴展原有的業務,
舉個例子:買二手房的時候不是直接跟房東接觸,而是由房產中介幫忙接觸,這樣可以省去很多事,資金的安全保障,房產證的辦理等,
我:飛姐真棒,那飛姐知道代理分成靜態代理和動態代理嗎?
飛姐:必須的啊,
靜態代理
飛姐:
由一個固定的代理幫忙控制目標物件,功能單一,不易擴展,所以稱之為靜態代理,如果還是很抽象的話,看一下下面的類圖就懂了:
Client 通過ProsySubject實作對RealSubject的訪問,Client無需要直接訪問RealSubject,這里需要注意:為了實作靜態代理模式,ProxySubject與RealSubject都必須實作Subject介面,
我:飛姐,能舉一個再具體一點的例子嗎?
飛姐:沒問題,現在上海房價貴的嚇人,把你賣了估計都不能外環外買一個老破小呢,這里就以買房為例吧,
示例
客戶通過中介購房二手商品房,客戶找的是一個夫妻中介(假設只能做一些二手商品房的交易),
public interface Owner {
void exchangeOldHouse(String str);
}
public class HouseOwner implements Owner {
@Override
public void exchangeOldHouse(String str) {
System.out.println(str);
}
}
public class CoupleAgent implements Owner {
//這里必須要包含被真正訪問的物件
HouseOwner houseOwner;
public CoupleAgent(HouseOwner houseOwner) {
super();
this.houseOwner = houseOwner;
}
@Override
public void exchangeOldHouse(String str) {
//擴展原有的業務1
doSomeThingBefore();
houseOwner.exchangeOldHouse(str);
//擴展原有的業務2
doSomeThingAfter();
}
private void doSomeThingBefore() {
System.out.println("收點中介費");
}
private void doSomeThingAfter() {
System.out.println("辦理產證");
}
}
public class Client {
public static void main(String[] args) {
//真正提供服務的物件
HouseOwner owner = new HouseOwner();
//由代理來接觸真正的物件
CoupleAgent agent = new CoupleAgent(owner);
agent.exchangeOldHouse("房子交易成功");
}
}
整個程序很好理解,通過代理客戶成功買到了房子,
我:確實很好理解,但萬一需求變了,來了一個大客戶,需要買辦公樓,當前的Owner只能提供二手商品房交易,無法提供辦公樓的交易,那怎么辦呢?難道非得讓Owner偷一個座辦公樓嗎?說的專業一點,難道要對介面進行擴展,添加新的方法,這很顯然違背了軟體開發程序的開閉原則啊(對擴展開放,對修改關閉),在已設計好的介面基礎之上,我們能擴展它,而不能去修改它,不然添加一個函式,之前實作此介面的類都需要進行修改,作業量非常大,
飛姐:你說的非常對,針對這種情況,我們可以讓中介(代理)去找辦公樓資源啊,客戶還是只需要跟中介溝通,但是我們這里的中介能力太小,只能處理二手商品房交易,如果有一個能根據不同的客戶而提供不同房源的中介豈不是相當完美,其實,這就是動態代理的概念,會根據需求的變化而變化,是一個動態的程序,
我:飛姐,可以介紹一下嗎,
飛姐:OK,我們繼續往下走,
動態代理
飛姐:在了解動態代理之前,希望大家有一定java反射機制以及類加載機制知識,如果沒有的話也沒關系,大家知道動態代理是怎么一回事就可以了,先當成一個公式使用,
我:感覺有點難啊
飛姐:是的,個人覺得動態代理比較難,目前提供動態的中間件有cglib(使用繼承實作的)和jdk(使用反射實作的),這里介紹jdk的動態代理,它使用反射機制創建代理類物件,并動態的指定要代理的目標類,在靜態代理中,目標類都是固定的,就像上文中的HouseOwner一樣,CoupleAgent 每次只能使用HouseOwner ,
我:有點感覺了,那為什么叫動態呢?
飛姐:這是個好問題,因為這種機制只有在程式執行時才能夠使用,即類需要加載到記憶體中才能用,java創建物件的程序是在java檔案編譯為class檔案后使用構造方法來創建,但動態代理不是這樣的,是程式加載到記憶體中使用反射來創建代理物件,有點繞,
我:有點期待哦,那jdk動態代理到底是怎么回事呢,
飛姐:來了來了,jdk的java.lang.reflect包中提供了三個類:InvocationHandler,Method,Proxy,整個動態代理就是圍繞這三個類完成的,
InvocationHandler: 它是一個介面,只有一個方法invoke(Object proxy,Metho method,Object[] args),這個方法最終是由jdk去呼叫,我們只需要知道我們需要重寫它,理解每個引數的意思就行.代理類中要完成的功能就寫在invoke方法中,因此可以把這個Handler看作代理類的功能,代理類完成的功能跟靜態代理一樣:呼叫目標方法,業務擴展,這三個引數的含義分別為:proxy:jdk創建的代理物件,無需賦值,method:目標類中的方法,jdk提供method物件,args:目標類中的方法引數,jdk提供的,
Method類:表示目標類中的方法,通過它可以執行目標類中的方法,Method.invoke(目標物件,方法的引數),相當于上文中的houseOwner.exchangeOldHouse(str);
Proxy類:用于創建代理物件,之前創建物件用new,這里使用Proxy的靜態方法newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h),回傳值為目標類的代理物件,相當于上文中的HouseOwner owner = new HouseOwner(),其ClassLoader為類加載器,負責向記憶體中加載目標類(因為在程式執行時才能使用動態代理,所以這里要傳入類加載器),例如HouseOwner.getClass().getClassLoader(),Class<?>[]為目標類實作的介面,也是反射獲取的,InvocationHandler 是我們自己寫的,代理類要完成的功能,
我:感覺好復雜啊
飛姐:有點吧,但是大家使用的時候遵循下面的步驟基本沒有問題,
- 創建介面,定義目標類要實作的功能
- 創建目標類,實作介面,重寫方法
- 創建InvocationHandler介面實作類(可以理解為代理的功能類,因為代理的功能都會定義此實作類的invoke方法中),在invoke方法中完成代理類的功能( 呼叫目標方法,擴展業務)
- 使用Proxy類的靜態方法,創建代理物件,并把回傳值轉為目標介面型別,
我:飛姐,舉個例子吧
飛姐:回到動態代理的開頭,來了一個大客戶,我們不可能去修改Owner介面,這不符合開閉原則,所要重新定義一個Owner實作類,但CoupleAgent又無法使用,見下面代碼,
public class OfficeOwner implements Owner {
@Override
public void exchangeOldHouse(String str) {
System.out.println("Here has kinds of office");
System.out.println(str);
}
}
/**
* 這是一個代理,通過這個代理可以與目標類取得聯系
*/
public class EstateHandler implements InvocationHandler {
//目標物件
private Object target;
/**
* obj:目標物件通過構造方法傳進來
*/
public EstateHandler(Object obj) {
super();
this.target = obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//擴展的業務1
doSomeThingBefore();
//目標方法,使用代理物件呼叫方法時,會通過反射生成method,從而保證能呼叫到目標函式
Object result = method.invoke(target, args);
//擴展的業務2
doSomeThingAfter();
return result;
}
private void doSomeThingBefore() {
System.out.println("收點中介費");
}
private void doSomeThingAfter() {
System.out.println("辦理產證");
}
}
飛姐:為了驗證動態代理的動態性,我在這里又加了一個房車客戶,并將之前例子用動態代理實作了一遍
public class HouseVanOwner implements Owner {
@Override
public void exchangeOldHouse(String str) {
System.out.println("Here is a house van");
System.out.println(str);
}
}
public class Client2 {
public static void main(String[] args) {
// 創建目標物件
Owner officeOwner = new OfficeOwner();
// 創建一個InvocationHandler物件
InvocationHandler estateHandler = new EstateHandler(officeOwner);
// 創建一個代理物件,知道這么用就行了,當作一個公式用,往下研究很復雜,先不考慮
Owner officeProxyInstance = (Owner) Proxy.newProxyInstance(officeOwner.getClass().getClassLoader(),
officeOwner.getClass().getInterfaces(), estateHandler);
// 使用代理物件呼叫目標方法,此目標方法會被反射成invoke方法中的method
officeProxyInstance.exchangeOldHouse("I am a rich client");
System.out.println("====================================================");
// 之前的靜態代理用動態代理處理試試
Owner houseOwner = new HouseOwner();
InvocationHandler houseHandler = new EstateHandler(houseOwner);
Owner houseProxyInstance = (Owner) Proxy.newProxyInstance(houseOwner.getClass().getClassLoader(),
houseOwner.getClass().getInterfaces(), houseHandler);
// 使用代理物件呼叫目標方法,此目標方法會被反射成invoke方法中的method
houseProxyInstance.exchangeOldHouse("I am a poor client");
System.out.println("====================================================");
// 如果又來個客戶需要買房車,這里只需要重新實作一下Owner介面,無需修改Owner介面了,符合開閉原則
Owner houseVanOwner = new HouseVanOwner();
InvocationHandler houseVanHandler = new EstateHandler(houseVanOwner);
Owner houseVanProxyInstance = (Owner) Proxy.newProxyInstance(houseVanOwner.getClass().getClassLoader(),
houseVanOwner.getClass().getInterfaces(), houseVanHandler);
// 使用代理物件呼叫目標方法,此目標方法會被反射成invoke方法中的method
houseVanProxyInstance.exchangeOldHouse("I am a car house client");
}
}
飛姐:好了,到這動態代理介紹的差不多了,提醒大家一點,debug時動態代理物件跟new出來的物件不一樣,留意一下就可以了,
我:感謝飛姐的細心講解,很精彩,期待下一個設計模式,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/286276.html
標籤:java



