代理模式詳解
目錄
1、什麼是代理模式
代理模式是指爲其他對象提供一種代理,以控制這個對象的訪問。代理對象在客戶端和目標對象之間起到一種中介的作用。他屬於結構型設計模式。
舉個例子就是,想象一下我們生活中的購買火車票的情節,我們可以從官網上直接購買,也可以到售票的窗口購買,如果從官網購買,那麼這就可以認爲是一個代理,代理對象就是app。
當然了,我們也可以從售票廳旁邊的小超市,或者其他地方代售火車票的地方購買火車票,那麼這些代銷處就是代理了火車站的售票功能,那麼他們就是代理。
2、結構圖
- 代理模式共分爲四種角色:
- Subject(抽象主題角色):真實主題角色與代理主題角色的共同父類,可以是具體類、抽象類、接口,客戶端針對抽象主題角色編程
- RealSubject(真實主題角色):實現具體業務方法的類,被代理主題角色調用
- Proxy(代理主題角色):持有真實主題角色的引用,在調用真實主題角色的具體業務方法之前或者之後添加其它操作
- 客戶端面對抽象主題角色編程,調用抽象主題角色實際上調用的是代理主題角色,代理
3、靜態代理實例
下面代碼演示的內容:父親代理兒子找對象。
public interface IPersion {
public void findWife();
}
/**
* @description: 目標類
* @author: zps
* @create: 2020-05-02 23:13
**/
public class Son implements IPersion {
@Override
public void findWife() {
System.out.println("兒子找對象的要求:白富美");
}
}
/**
* @description: 代理類
* @author: zps
* @create: 2020-05-02 23:14
**/
public class Father implements IPersion {
private Son son;
public Father(Son son){
this.son = son;
}
@Override
public void findWife() {
System.out.println("開始幫兒子找對象");
son.findWife();
System.out.println("找到了對象");
}
}
**
* @description: 測試
* @author: zps
* @create: 2020-05-02 23:16
**/
public class Test {
public static void main(String args[]){
Father father = new Father(new Son());
father.findWife();
}
}
運行結果:
開始幫兒子找對象
兒子找對象的要求:白富美
找到了對象
4、動態代理實例
對代理模式而言,一般來說,具體主題類與其代理類是一一對應的,這也是靜態代理的特點。但是,也存在這樣的情況:有N個主題類,但是代理類中的“預處理、後處理”都是相同的,僅僅是調用主題不同。那麼,若採用靜態代理,那麼必然需要手動創建N個代理類,這顯然讓人相當不爽。動態代理則可以簡單地爲各個主題類分別生成代理類,共享“預處理,後處理”功能,這樣可以大大減小程序規模,這也是動態代理的一大亮點。
在動態代理中,代理類是在運行時期生成的。因此,相比靜態代理,動態代理可以很方便地對委託類的相關方法進行統一增強處理,如添加方法調用次數、添加日誌功能等等。動態代理主要分爲JDK動態代理和cglib動態代理兩大類,本文主要對JDK動態代理進行探討。
代碼演示:
public interface IPersion {
public void findWife();
}
/**
* @description: 目標類
* @author: zps
* @create: 2020-05-02 23:32
**/
public class Zhangsan implements IPersion {
@Override
public void findWife() {
System.out.println("白富美");
}
}
/**
* @description: 代理類
* @author: zps
* @create: 2020-05-02 23:33
**/
public class JdkMeipo implements InvocationHandler {
private IPersion target;
public Object getInstance(IPersion target){
this.target = target;
Class<?> clazz = target.getClass();
//返回一個動態代理對象
return Proxy.newProxyInstance(clazz.getClassLoader() ,
clazz.getInterfaces() ,
this);
}
/**
* proxy 最終生成的代理實例
* method 被代理目標實例的某個具體方法,通過它可以發起目標實例方法的反射調用。
* args 被代理實例某個方法的入參,在方法反射調用時使用。
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("找對象中");
Object obj = method.invoke(this.target , args);
System.out.println("找到對象了");
return obj;
}
}
public class Test {
public static void main(String args[]){
JdkMeipo jdkMeipo = new JdkMeipo();
IPersion zhangsan = (IPersion) jdkMeipo.getInstance(new Zhangsan());
zhangsan.findWife();
}
}
運行結果:
找對象中
白富美
找到對象了
5、JDK動態代理和Cglib的區別
不僅知其然,還得知其所以然。既然JDK動態代理功能如此強大,那麼它是如何實現的呢?我們現 在來探究一下原理。
我們都知道JDK動態代理採用字節重組,重新生成對象來替代原始對象,以達到動態代理的目的。
JDK動態代理生成對象的步驟如下:
(1)獲取被代理對象的引用,並且獲取它的所有接口,反射獲取。
(2)JDK動態代理類重新生成一個新的類,同時新的類要實現被代理類實現的所有接口。
(3)動態生成Java代碼,新加的業務邏輯方法由一定的邏輯代碼調用(在代碼中體現)。
(4)編譯新生成的Java代碼.class文件。
(5)重新加載到JVM中運行。
以上過程就叫字節碼重組。JDK中有一個規範,在ClassPath下只要是$開頭的.class文件,一般都是自動生成的。那麼珊門有沒有辦法看到代替後的對象的"真容"呢?做一個這樣測試,我們將內存中的對象字節碼通過文件流輸出到一個新的.class文件,然後^用反編譯工具查看其源代碼。
CGLib和JDK動態代理對比:
(1 ) JDK動態代理實現了被代理對象的接口,CGLib代理繼承了被代理對象。
(2 )JDK動態代理和CGLib代理都在運行期生成字節碼JDK動態代理直接寫Class字節碼,CGLib 代理使用ASM框架寫Class字節碼,CGlib代理實現更復雜,生成代理類比JDK動態代理效率低。
(3 )JDK動態代理調用代理方法是通過反射機制調用的,CGLib代理是通過FastClass機制直接調 用方法的,CGLib代理的執行效率更高。
代理模式在Spring中的應用:
先看ProxyFactoryBean核心方法getObject(),源碼如下:
@Nullable public Object getObject() throws BeansException { this.initializeAdvisorChain(); if (this.isSingleton()) { return this.getSingletonInstance(); } else { if (this.targetName == null) { this.logger.info("Using non-singleton proxies with singleton targets is often undesirable. Enable prototype proxies by setting the 'targetName' property."); } return this.newPrototypeInstance(); } }
在 getObjectO方法中,主要調用 getSingletonlnstance0和 newPrototypeInstanceO()。 在 Spring 的配置中如果不做任何設置,那麼Spring代理生成的Bean都是單例對象。如果修改scope,則每次 創建一個新的原型對象。newPrototypelnstanceQ裏面的邏輯比較複雜,在這不多加研究。
Spring利用動態代理實現AOP時有兩個非常重要的類:JdkDynamicAopProxy類和
CglibAopProxy類,來看一下類圖,如下圖所示。
.Spring中的代理選擇原則
(1 )當Bean有實現接口時,Spring就會用JDK動態代理。
(2 )當 Bean 沒有實現接口時,Spring CGLib 牌。
(3 ) Spring可以通過配置強制使用CGLib代理,只需在Spring的配置文件中加入如下代碼:
<aop: a spec tj-a utoproxy proxy-target-class="true"/>
靜態代理和動態代理的本質區別
(1)靜態態代理只能通過手動完成代理操作,如果被代理類增加了新的方法,代理類需要同步增加,
違背開閉原則。
(2)動態代理採用在運行時動態生成代碼的方式,取消了對被代理類的擴展限制,遵循開閉原則。
(3) 若動態代理要對目標類的增強邏輯進行擴展,結合策略模式,只需要新增策略類便可完成,無
須修改代理類的代碼。
代理模式的優缺點
代理模式具有以下優點:
(1 )代理模式能將代理對象與真實被調用目標對象分離。
(2 )在一定程度上降低了系統的耦合性,擴展性好。
(3 )可以起到保護目標對象的作用。
(4 )可以增強目標對象的功能。
當然,代理模式也有缺點:
(1 )代理模式會造成系統設計中類的數量增加。
(2 )在客戶端和目標對象中增加一個代理對象,會導致請求處理速度變慢。
(3 )增加了系統的複雜度。