要理解和說明什么是動態(tài)代理需要先解釋面向?qū)ο笾谐R姷脑O計模式------------代理模式
什么是代理模式(Proxy)
定義:給目標對象提供一個代理對象思灰,并由代理對象控制對目標對象的引用
主要目的是為了在不改變對象具體方法的情況下實現(xiàn)對目標方法的增強,或解決直接訪問目標對象帶來的問題鉴未。
如下UML圖揭示了用戶,接口鸠姨,委托類和代理之間的關系铜秆。
需要注意的是:
- 用戶僅通過接口調(diào)用接口功能,不在乎是誰提供了具體功能讶迁;
2.RealSubject是接口的真正實現(xiàn)類连茧,但不和用戶真正發(fā)生接觸
3.Proxy同樣也實現(xiàn)了接口,所以可以和用戶直接接觸
4.用戶調(diào)用Proxy時巍糯,Proxy內(nèi)部調(diào)用了RealSubject的功能啸驯,且可以在此基礎上實現(xiàn)功能的增強
代理關系的直白理解:
如上圖,原本廠家生產(chǎn)電腦直接銷售給顧客祟峦,廠家和顧客之間直接聯(lián)系(類似最基本的調(diào)用接口的實現(xiàn)類)坯汤。但因為需要提供后續(xù)的售后和維修服務,增加了廠家的成本(開始的實現(xiàn)類功能不夠了)搀愧。廠家為了削減成本,減輕管理負擔(避免在現(xiàn)有實現(xiàn)類上的直接修改),廠家將部分業(yè)務交給代理去完成咱筛,而廠家也不再直接接觸顧客搓幌,僅將電腦提供給代理商,由代理商完成限售和保障售后服務(代理在現(xiàn)有類的基礎上提供更多的功能)迅箩。
靜態(tài)代理
如下是廠家的接口溉愁,提供了銷售電腦的方法。同時一個通用的接口是實現(xiàn)代理的基礎
package com.ed.demo;
public interface IProducer {
public void sellComputer();
}
接下來是廠家的真正實現(xiàn)類饲趋,和實現(xiàn)接口的代理類
package com.ed.demo.impl;
import com.ed.demo.IProducer;
public class IProducerImpl implements IProducer {
@Override
public void sellComputer() {
System.out.println("出售了一臺電腦");
}
}
實現(xiàn)接口的代理類拐揭,在售賣電腦的前提下增加了售前和售后服務
package com.ed.demo.impl;
import com.ed.demo.IProducer;
public class Seller implements IProducer {
IProducerImpl iProducer;
public Seller(IProducerImpl iProducer) {
super();
this.iProducer = iProducer;
}
@Override
public void sellComputer() {
System.out.println("進行售前引導"); //Seller額外提供的售前服務
iProducer.sellComputer(); //廠家原來的銷售電腦功能
System.out.println("進行售后服務"); //Seller額外提供的售后服務
}
}
那么運行通過代理方式的調(diào)用
package com.ed.demo;
import com.ed.demo.impl.IProducerImpl;
import com.ed.demo.impl.Seller;
public class ProxyTestDemo {
public static void main(String[] args) {
IProducerImpl iProducer = new IProducerImpl();
Seller seller = new Seller(iProducer);
seller.sellComputer();
}
}
結(jié)果如下:
進行售前引導
出售了一臺電腦
進行售后服務
現(xiàn)在可以看到,代理模式可以在不修改被代理對象的基礎上奕塑,通過擴展代理類堂污,進行一些功能的附加與增強。值得注意的是龄砰,代理類和被代理類應該共同實現(xiàn)一個接口盟猖,或者是共同繼承某個類。
但靜態(tài)代理仍然存在一些缺陷换棚,如:
- 代理類和委托類實現(xiàn)了相同的接口式镐,代理類通過委托類實現(xiàn)了相同的方法。這樣就出現(xiàn)了大量的代碼重復固蚤。如果接口增加一個方法娘汞,除了所有實現(xiàn)類需要實現(xiàn)這個方法外,所有代理類也需要實現(xiàn)此方法夕玩。增加了代碼維護的復雜度你弦。
- 代理對象只服務于一種類型的對象,如果要服務多類型的對象风秤。勢必要為每一種對象都進行代理宵蕉,靜態(tài)代理在程序規(guī)模稍大時就無法勝任了烤礁。
動態(tài)代理
在上述靜態(tài)代理中,一個靜態(tài)代理只能服務于一個接口,實際開發(fā)中必然導致代理類過多具被。
動態(tài)代理利用反射機制,在程序運行時才根據(jù)需求實現(xiàn)一個被代理對象的接口佃扼,而不需要定義具體的代理類(上述中的Seller)激涤。
下面通過動態(tài)代理來實現(xiàn)經(jīng)銷商的功能,首先創(chuàng)建一個InvocationHandler的實現(xiàn)類
package com.ed.demo.impl;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class MyInvocationHandler implements InvocationHandler {
private Object producer;
public MyInvocationHandler(Object producer) {
this.producer = producer;
}
/**
*
* @param proxy: 被代理的對象
* @param method: 要調(diào)用的方法
* @param args: 方法調(diào)用時所需要參數(shù)
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("進行售前服務");
method.invoke(producer, args);
System.out.println("進行售后服務");
return null;
}
}
InvocationHandler 內(nèi)部只包含一個 invoke() 方法累提,正是這個方法決定了怎么樣處理代理傳遞過來的方法調(diào)用尘喝。
- proxy 代理對象
- method 代理對象調(diào)用的方法
- args 調(diào)用的方法中的參數(shù)
因為,Proxy 動態(tài)產(chǎn)生的代理會調(diào)用 InvocationHandler 實現(xiàn)類斋陪,所以 InvocationHandler 是實際執(zhí)行者朽褪。
再按如下方式進行調(diào)用
import java.lang.reflect.Proxy;
public class ProxyTestDemo {
public static void main(String[] args) {
IProducerImpl iProducer = new IProducerImpl();
InvocationHandler handler = new MyInvocationHandler(iProducer);
IProducer producer =(IProducer) Proxy.newProxyInstance(IProducerImpl.class.getClassLoader(),
IProducerImpl.class.getInterfaces(),
handler);
producer.sellComputer();
}
}
結(jié)果如下:
進行售前引導
出售了一臺電腦
進行售后服務
其中最重要的方法是
IProducer producer =(IProducer) Proxy.newProxyInstance(IProducerImpl.class.getClassLoader(),
IProducerImpl.class.getInterfaces(),
handler);
第一個參數(shù)傳入了委托類的類加載器置吓,運行時負責將字節(jié)碼加載到JVM中并為其定義類對象。
第二個參數(shù)返回了委托類對象所引用的類實現(xiàn)的所有接口缔赠。
第三個參數(shù)是調(diào)用處理器接口衍锚,它自定義了一個 invoke 方法,用于集中處理在動態(tài)代理類對象上的方法調(diào)用嗤堰。在此處中實現(xiàn)了原方法的增強戴质。
對于動態(tài)代理而言,上述代碼中 前兩個參數(shù)幾乎是固定的踢匣。(先傳入類加載器告匠,后傳入全部接口)
如果此時廠家同時售賣顯示器
package com.ed.demo;
public interface IProducer {
public void sellComputer();
public void sellMonitor();
}
委托類中進行實現(xiàn)
import com.ed.demo.IProducer;
public class IProducerImpl implements IProducer {
@Override
public void sellComputer() {
System.out.println("出售了一臺電腦");
}
@Override
public void sellMonitor() {
System.out.println("出售了一臺顯示器");
}
}
這次調(diào)用sellMonitor()
package com.ed.demo;
import com.ed.demo.impl.IProducerImpl;
import com.ed.demo.impl.MyInvocationHandler;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class ProxyTestDemo {
public static void main(String[] args) {
IProducerImpl iProducer = new IProducerImpl();
InvocationHandler handler = new MyInvocationHandler(iProducer);
IProducer producer =(IProducer) Proxy.newProxyInstance(IProducerImpl.class.getClassLoader(),
IProducerImpl.class.getInterfaces(),
handler);
producer.sellMonitor();
}
}
可以看到代理仍然提供了售前售后服務
進行售前服務
出售了一臺顯示器
進行售后服務
而且如果有另外的Iproducer實現(xiàn)類作為新的委托類,也可以以同樣的方法享受代理服務离唬,這里不再演示后专。
有選擇性的中轉(zhuǎn)
在上述代碼基礎上,對MyInvocationHandler做如下修改
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if(method.getName().equals("sellMonitor")){
//如果是出售顯示器則不進行售后服務
method.invoke(producer, args);
return null;
}
System.out.println("進行售前服務");
method.invoke(producer, args);
System.out.println("進行售后服務");
return null;
}
}
不對銷售的顯示器進行售后服務男娄。那么再次調(diào)用
public class ProxyTestDemo {
public static void main(String[] args) {
IProducerImpl iProducer = new IProducerImpl();
InvocationHandler handler = new MyInvocationHandler(iProducer);
IProducer producer =(IProducer) Proxy.newProxyInstance(IProducerImpl.class.getClassLoader(),
IProducerImpl.class.getInterfaces(),
handler);
producer.sellMonitor();
}
}
則結(jié)果中就沒有售前售后服務
出售了一臺顯示器
可以看到利用動態(tài)代理某個接口下的多個引用可以集中到一處進行方法增強或者有選擇性的進行代理工作行贪。
動態(tài)代理的優(yōu)點
與靜態(tài)代理相比較,最大的好處是接口中的所有方法都轉(zhuǎn)移到調(diào)用處理器的一個集中的方法(InvocationHandler.invoke)進行處理模闲。同時接口方法數(shù)量較多時也可以靈活處理建瘫。
部分源碼解析
未完待續(xù)