1 什麼是代理模式
代理模式的定義:
代理模式給某一個物件提供一個代理物件,並由代理物件控制對原物件的引用。舉例說明,就是一個人或者一個機構代表另一個人或者另一個機構採取行動。在一些情況下,一個客戶不想或者不能夠直接引用一個物件,而代理物件可以在客戶端和目標物件之前起到中介的作用。
代理模式的關鍵點:
代理物件與目標物件.代理物件是對目標物件的擴充套件,並會呼叫目標物件
2 為什麼使用代理
我們在寫一個功能函式時,經常需要在其中寫入與功能不是直接相關但很有必要的代 碼,如日誌記錄,資訊傳送,安全和事務支援等,這些枝節性程式碼雖然是必要的,但它會帶來以下麻煩:
- 枝節性程式碼遊離在功能性程式碼之外,它不是函式的目的,這是對OO是一種破壞
- 枝節性程式碼會造成功能性程式碼對其它類的依賴,加深類之間的耦合,可重用性降低
- 從法理上說,枝節性程式碼應該監視著功能性程式碼,然後採取行動,而不是功能性程式碼 通知枝節性程式碼採取行動,這好比吟遊詩人應該是主動記錄騎士的功績而不是騎士主動要求詩人記錄自己的功績.
靜態代理和動態代理的區別
- Proxy類的程式碼被固定下來,不會因為業務的逐漸龐大而龐大;
- 可以實現AOP程式設計,這是靜態代理無法實現的;
- 解耦,如果用在web業務下,可以實現資料層和業務層的分離。
- 動態代理的優勢就是實現無侵入式的程式碼擴充套件。
靜態代理這個模式本身有個大問題,如果類方法數量越來越多的時候,代理類的程式碼量是十分龐大的。所以引入動態代理來解決此類問題
3 靜態代理
靜態代理在使用時,需要定義介面或者父類,被代理物件與代理物件一起實現相同的介面或者是繼承相同父類.
關鍵:在編譯期確定代理物件,在程式執行前代理類的.class檔案就已經存在了。
應用場景舉例:
以買房子為例,在實際生活中,我們為例方便買房子會通過中介,中介就充當代理角色.
- 首先建一個買房介面
package com.itqf.testStaticProxy;
//介面 規定買房子方法
public interface IBuyHouse {
void buyHouse();
}
複製程式碼
- 建立一個客戶類,繼承IBuyHouse
package com.itqf.testStaticProxy;
//消費者買房子
public class Costomer implements IBuyHouse {
//買房子需要多少錢
private int money;
public int getMoney() {
return money;
}
public void setMoney(int money) {
this.money = money;
}
@Override
public void buyHouse() {
// TODO Auto-generated method stub
System.out.println("買房子花了"+money+"元");
}
}
複製程式碼
- 建立代理物件
package com.itqf.testStaticProxy;
//中介類 代理
public class BuyHouseProxy implements IBuyHouse{
private Costomer costomer;
public BuyHouseProxy(Costomer costomer) {
this.costomer = costomer;
}
@Override
public void buyHouse() {
// TODO Auto-generated method stub
System.out.println("中介給客戶介紹房源");
costomer.buyHouse();
System.out.println("客戶買房成功");
}
}
複製程式碼
- 測試
package com.itqf.testStaticProxy;
public class TestBuyHouse {
public static void main(String[] args) {
Costomer costomer = new Costomer();
costomer.setMoney(1000000);
BuyHouseProxy proxy = new BuyHouseProxy(costomer);
proxy.buyHouse();
}
}
複製程式碼
中介給客戶介紹房源
買房子花了1000000元
客戶買房成功
靜態代理總結:
- 可以做到在不修改目標物件的情況下,對目標物件進行功能擴充套件
- 缺點:代理類和委託類實現相同的介面,同時要實現相同的方法。這樣就出現了大量的程式碼重複。如果介面增加一個方法,除了所有實現類需要實現這個方法外,所有代理類也需要實現此方法。增加了程式碼維護的複雜度。
4 動態代理
4.1 動態代理的特點
- 在執行期,通過反射機制建立一個實現了一組給定介面的新類
- 在執行時生成的class,必須提供一組interface給它,然後該class就宣稱它實現了這些 interface。該class的實 例可以當作這些interface中的任何一個來用。但是這個Dynamic Proxy其實就是一個Proxy, 它不會替你作實質性的工作,在生成它的例項時你必須提供一個handler,由它接管實際的工 作。
- 動態代理也叫做:JDK代理,介面代理
- 介面中宣告的所有方法都被轉移到呼叫處理器一個集中的方法中處理(InvocationHandler.invoke)。這樣,在介面方法數量比較多的時候,我們可以進行靈活處理,而不需要像靜態代理那樣每一個方法進行中轉。而且動態代理的應用使我們的類職責更加單一,複用性更強
JDK中生成代理物件的API
代理類所在包:java.lang.reflect.Proxy
JDK實現代理只需要使用newProxyInstance方法
static Object newProxyInstance(ClassLoader loader, Class [] interfaces, InvocationHandler handler)
複製程式碼
注意該方法是在Proxy類中是靜態方法,且接收的三個引數:
- ClassLoader loader:指定當前目標物件使用類載入器,用null表示預設類載入器
- Class [] interfaces:需要實現的介面陣列
- InvocationHandler handler:呼叫處理器,執行目標物件的方法時,會觸發呼叫處理器的方法,從而把當前執行目標物件的方法作為引數傳入
java.lang.reflect.InvocationHandler: 這是呼叫處理器介面,它自定義了一個 invoke 方法,用於集中處理在動態代理類物件上的方法呼叫,通常在該方法中實現對委託類的代理訪問。
// 該方法負責集中處理動態代理類上的所有方法呼叫。第一個引數既是代理類例項,第二個引數是被呼叫的方法物件.第三個方法是呼叫引數。
Object invoke(Object proxy, Method method, Object[] args)
複製程式碼
場景應用:還是以買房子為例
- 首先建一個買房介面
package com.itqf.testDynamicProxy;
//介面 規定買房子方法
public interface IBuyHouse {
void buyHouse();
}
複製程式碼
- 建立消費者
package com.itqf.testDynamicProxy;
//消費者買房子
public class Costomer implements IBuyHouse {
//買房子需要多少錢
private int money;
public int getMoney() {
return money;
}
public void setMoney(int money) {
this.money = money;
}
@Override
public void buyHouse() {
// TODO Auto-generated method stub
System.out.println("買房子花了"+money+"元");
}
}
複製程式碼
- 建立代理工廠類:
package com.itqf.testDynamicProxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
//實現InvocationHandler介面
public class ProxyFactory implements InvocationHandler{
private Object obj;
public ProxyFactory(Object obj) {
this.obj = obj;
}
/*
重寫InvocationHandler介面中的invoke()方法,
動態代理模式可以使得我們在不改變原來已有的程式碼結構的情況下,對原來的“真實方法”進行擴充套件、增強其功能,並且可以達到控制被代理物件的行為.
*/
@Override
public Object invoke(Object o, Method method, Object[] args) throws Throwable {
System.out.println("中介給客戶介紹房源");
Object invoke = method.invoke(this.obj, args);
System.out.println("客戶買房成功");
return invoke;
}
}
複製程式碼
- 測試
package com.itqf.testDynamicProxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class TestBuyHouse {
public static void main(String[] args) {
Costomer costomer = new Costomer();
costomer.setMoney(1000000);
InvocationHandler factory = new ProxyFactory(costomer);
//使用Proxy的newProxyInstance()方法
IBuyHouse buyhouse =(IBuyHouse) Proxy.newProxyInstance(costomer.getClass().getClassLoader(), costomer.getClass().getInterfaces(), factory);
buyhouse.buyHouse();
}
}
複製程式碼
中介給客戶介紹房源
買房子花了1000000元
客戶買房成功
4.2 動態代理的好處
- 減少程式設計的工作量:假如需要實現多種代理處理邏輯,只要寫多個代理處理器就可以了,無需每種方式都寫一個代理類。
- 系統擴充套件性和維護性增強,程式修改起來也方便多了(一般只要改代理處理器類就行了)。