代理模式 PROXY 別名Surrogate
意圖
爲其餘的對象提供一種代理以控制對這個對象的訪問。
代理模式含義比較清晰,就是中間人,中介公司,經紀人...
在計算機程序中,代理就表示一個客戶端不想或者不可以直接引用一個對象
而代理對象能夠在客戶端和目標對象之間起到中介的做用
結構
代理模式的根本在於隔離,以下圖所示,間接訪問
代理對象如何可以真的代理真實對象?
在Java語言中,看起來像的一個方式就是實現同一接口
代理角色和真實對象角色擁有共同的抽象類型,他們擁有相同的對外接口request()方法
ProxySubject內部擁有一個RealSubject
你應該能感受到組合模式的思想-----他們都是Subject,屬於同一個Component
對外有一致的接口
抽象主題角色Subject
聲明瞭真實主題和代理主題的共同接口,任何使用真實主題的地方,均可以使用代理主題
代理主題角色ProxySubject
代理主題角色內部含有對真實對象的引用,從而能夠在任什麼時候候操做真實主題
代理主題提供與真實主題的相同的接口,以便任什麼時候刻,均可以替代真實主題
並且,代理主題還能夠在真實主題執行先後增長額外的處理,好比:經紀人要先收下費~
真實主題角色RealSubject
被代理的真實主題對象,真正工做的是他,好比經紀人總不會站在舞臺上去~
示例代碼
Subject 抽象角色 定義了真正的處理請求 的request()方法
package proxy;
public interface Subject {
void request();
}
RealSubject真實主題角色,實現了處理請求的方法
package proxy;
public class RealSubject implements Subject {
@Override
public void request() {
System.out.println("realSubject process request....");
}
}
Proxy代理角色
實現了request()方法,用於替代真實主題,內部調用真實主題完成請求
而且額外的提供了pre和after操做
package proxy;
public class Proxy implements Subject{
private Subject realSubject;
@Override
public void request() {
preRequest();
realSubject.request();
afterRequest();
}
public Proxy(Subject realSubject){
this.realSubject = realSubject;
}
public void preRequest(){
System.out.println("pre request do sth....");
}
public void afterRequest(){
System.out.println("after request do sth....");
}
}
測試類
package proxy;
public class Test {
/**請求subject執行請求
* @param subject
*/
public static void askForSth(Subject subject){
subject.request();
System.out.println("################");
}
public static void main(String[] args){
Subject real = new RealSubject();
Subject proxy = new Proxy(real);
askForSth(proxy);
askForSth(real);
}
}
定義了真實對象,也定義了一個代理對象
查看他們分別處理請求的結果
從下面的時序圖中,能更好的感覺到「間接」的感受
在真正調用真實對象方法前,須要先執行preRequest方法
真實對象方法調用後,在執行afterRequest方法
代理實現
代理的實現分類有兩種,靜態代理和動態代理
前面形式描述的代理,就是靜態代理
在編譯時期,就已經編寫生成好了代理類的源代碼,程序運行以前class文件就已經生成了
這種按照咱們上面模式編寫了代理類和真實類的形式就是 靜態代理
靜態代理常常被用來對原有邏輯代碼進行擴展,原有的邏輯不須要變動,可是能夠增長更多的處理邏輯
可是,可是若是有不少的對象須要被代理怎麼辦?
若是按照靜態代理的形式,那麼將會出現不少的代理類,勢必致使代碼的臃腫。
因此後來出現了動態代理
JDK代理機制
所謂動態代理,按照字面意思就是動態的進行代理,
動態相對於靜態的含義是不須要事先主動的建立代理類,
能夠在運行時須要的時候,動態的建立一個代理類。
動態代理的動態關鍵在於代理類的動態生成,不須要咱們實現建立,從class文件的角度來看的話,是與靜態代理同樣的,仍舊有一個代理類的Class文件
在Java中提供了內置的動態代理的支持。
Java在
java.lang.reflect包中提供了三個核心
Proxy,
InvocationHandler,
Method 能夠用於動態代理的使用
Java動態代理簡單示例
package proxy.MyDynamicProxy;
public interface Subject {
void doSth();
}
package proxy.MyDynamicProxy;
public class RealSubject implements Subject {
@Override
public void doSth() {
System.out.println("real Object do something...");
}
}
package proxy.MyDynamicProxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class DynamicProxyHandler implements InvocationHandler {
private Object realSubject;
public DynamicProxyHandler(Object realSubject) {
this.realSubject = realSubject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("proxy do something....");
return method.invoke(realSubject, args);
}
}
package proxy.MyDynamicProxy;
import java.lang.reflect.Proxy;
public class Test {
public static void main(String[] args){
RealSubject realSubject = new RealSubject();
Subject proxy = (Subject) Proxy
.newProxyInstance(Test.class.getClassLoader(), new Class[]{Subject.class}, new DynamicProxyHandler(realSubject));
proxy.doSth();
}
}
動態代理到底都作了什麼?
對於靜態代理,咱們有一個RealSubject,以及他的超接口Subject
Subject定義了方法,RealSubject實現了方法。
而後咱們建立了代理類,這個代理類實現了Subject接口,而且將新增的邏輯添加進來,而後經過代理類進行方法調用。
在上面的例子中,
RealSubject,以及他的超接口Subject含義不變,與靜態代理中的邏輯同樣。
而後咱們
建立了一個調用處理器DynamicProxyHandler 實現了 InvocationHandler接口
該接口只有一個方法
invoke
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
他有三個參數
proxy - 在其上調用方法的代理實例
method - 對應於在代理實例上調用的接口方法的 Method 實例。Method 對象的聲明類將是在其中聲明方法的接口,該接口能夠是代理類賴以繼承方法的代理接口的超接口。
args - 包含傳入代理實例上方法調用的參數值的對象數組,若是接口方法不使用參數,則爲 null。基本類型的參數被包裝在適當基本包裝器類(如 java.lang.Integer 或 java.lang.Boolean)的實例中。
最後經過Java提供的代理機制建立了一個代理
Subject proxy = (Subject) Proxy
.newProxyInstance(Test.class.getClassLoader(), new Class[]{Subject.class}, new DynamicProxyHandler(realSubject));
核心就是
newProxyInstance方法,他建立了一個實現了Subject接口的代理類
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
這個方法也有三個參數
loader - 定義代理類的類加載器
interfaces - 代理類要實現的接口列表
h - 指派方法調用的調用處理程序
爲何須要這三個參數呢?
首先,Proxy.newProxyInstance幫你動態的建立方法,確定要有一個類加載器,上面的示例中咱們直接使用的測試類的類加載,這個通常是應用程序 類加載器
再者,動態代理與靜態代理同樣,須要實現一樣的接口,那你實現了哪些接口呢?因此你得把接口列表告訴我
最後,你但願有哪些處理呢?你要把處理器給我
proxy.doSth();執行時,會將當前代理實例,以及當前方法,以及當前方法的參數傳遞給invoke方法,因此就完成代理的功能。
再來重頭理一下:
- 如同靜態代理,須要被代理的對象RealSubject,以及他的超接口Subject
- 須要實現InvocationHandler接口建立一個處理器,新增長的方法邏輯封裝在invoke方法中
- Proxy.newProxyInstance建立代理實例
- 使用建立的代理實例執行方法
簡言之,動態代理與靜態代理如出一轍,差異就在於不用你事先去本身主動地建立一個代理類
靜態的時候編寫了代理類,而後編譯爲class而後須要時被加載到JVM,而後調用
動態是運行時在須要的時候,直接生成class文件
依照上面的步驟流程,你就能夠藉助於Java的機制實現動態代理
可是你會發現,Proxy.newProxyInstance方法的參數須要一個 Class<?>[] interfaces,這意味着什麼?這意味着被代理的對象必須實現一個接口
若是被代理的對象未曾實現任何接口怎麼辦?
給每一個被代理的對象增長一個標記接口(形式接口)?若是隻是爲了使用JDK的動態代理實現,而添加了無心義的接口這是否穩當?
CGLIB
還有另一種形式的動態代理CGLIB
須要兩個Jar
package proxy.cglib;
public class RealSubject{
public void doSth() {
System.out.println("realSubject process request....");
}
}
package proxy.cglib;
import java.lang.reflect.Method;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
public class MyHandler implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy)
throws Throwable {
System.out.println("before do something...");
Object object = methodProxy.invokeSuper(o,objects);
System.out.println("after do something...");
return object;
}
}
package proxy.cglib;
import net.sf.cglib.proxy.Enhancer;
public class Test {
public static void main(String[] args){
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(RealSubject.class);
enhancer.setCallback(new MyHandler());
RealSubject subject = (RealSubject)enhancer.create();
subject.doSth();
}
}
在這個示例中,再也不須要接口,僅僅只有一個真是對象RealSubject
實現了一個處理器 MyHandler 繼承自 MethodInterceptor,實現了intercept方法
在測試客戶端中,經過四個步驟建立了代理對象,而後藉助於代理對象執行
從 enhancer.setSuperclass(RealSubject.class);這一句或許猜獲得,CGLIB不依賴於接口,而是代理類繼承了真實主題類
流程
真實主題對象RealSubject是必不可少的,不然代理模式就沒有意義了
相似JDK的代理模式,處理器也是解耦的,在CGLIB中藉助於MethodInterceptor接口約定,這一步要作的事情的本質與InvocationHandler並無什麼太多不一樣---封裝附加的處理邏輯
藉助於Enhancer用來組裝處理建立邏輯,而且建立代理類
setSuperclass設置須要繼承的類(也就是被代理的類)
setCallback設置回調函數
create建立真正的代理對象。
CGLIB採用繼承的機制,若是一個類是final的怎麼辦?那就歇菜了
JDK代理機制與CGLIB對比
目前到JDK8 聽說性能已經優於CGLIB了
JDK機制不須要第三方Jar,JDK默認集成,CGLIB須要引入第三方Jar包
JDK須要依賴真實主題對象實現接口,CGLIB則不須要,CGLIB繼承了真實主題
CGLIB雖然不依賴真實主題實現接口,可是被代理的類不能爲final,那樣的類是沒法繼承的
一般的作法是若是實現了接口,那麼使用JDK機制,若是沒有實現接口,使用CGLIB
代理用途分類
代理模式的根本在於隔離,「間接」,只要隔離,間接,那麼就能夠隱藏真實對象,而且增長額外的服務,優化,管理等
好比
隱藏了真實的對象,好比你經過中介租房子,可能到期也沒見過房東
提供了代理層,能夠
提供更多服務
好比買賣房屋經過中介能夠節省你合同的審校工做,不少人不懂合同中暗藏的貓膩
隱藏真實對象,天然可以起到必定的
保護做用,避免了直接接觸
好比去學校見孩子,須要先通過老師贊成
經過代理,也至關於有一個管家,能夠
管理外界對真實對象的接觸訪問
好比,真實對象是電腦,管家類軟件至關於代理,能夠限制小孩子對電腦的使用時長
圍繞着代理帶來的特色「隱藏真實對象,而且增長額外的服務,優化,限制」
在多種場景下,延伸出來一些分類
遠程代理 Remote
爲一個位於不一樣的地址空間的對象提供一個局域表明對象,這個不一樣的地址空間能夠是本機器的,也能夠是另外一臺機器的
虛擬代理 Virtual
根據須要建立一個資源消耗較大的對象,使得此對象只在須要時纔會被真正建立
保護代理 Protect or Access
控制對一個對象的訪問,若是須要,能夠給不一樣的用戶提供不一樣級別的使用權限
Cache代理
爲一個目標操做的結果提供臨時的存儲空間,以便多個客戶端能夠共享這些結果
防火牆代理 Firewall
保護目標,防止惡意行爲
同步代理 Synchronization
使幾個用戶可以同時使用一個對象而沒有衝突
智能引用 Smart Reference
當一個對象被引用時,提供一些額外的操做,好比將對象調用次數記錄下來
很顯然,這些分類其實只是代理的不一樣應用場景,之後可能還會有更多的分類出來
可是永遠也脫離不了代理的「隔離」「間接」的根本核心。
總結
代理角色雖然是真實角色的「代理人」,雖然代理角色內部依賴真實角色
可是真實角色能夠徹底脫離代理人,單獨出現
好比上面示例中的
askForSth(proxy); html
askForSth(real); java
只不過,經過代理角色會有不一樣的效果
代理人只是會「幫助」解決他能解決的問題,它能提供的服務,他作不了的事情
好比經紀人不會出唱片,對於出唱片的任務仍是會委託給真實角色
現實世界中,咱們一般說真實角色委託代理角色,好比,房東找中介
在程序世界中,一般卻說代理角色將任務委託給真實角色,委託與被委託都是相對的
要看你究竟是站在什麼視角看待問題,無所謂~
再次強調,代理模式的重點在於增長對真實受訪對象的控制,也能夠增長額外的服務。