千里之行,始于足下;
九層之臺,起于累土
任何事情都不能一蹴而就,物極必反的道理相信大家都或多或少的懂一些。
動態代理是一門較高級的技術,我們自己在平時的開發中也許很少用到,但是在你使用的開源工具包中也許就有它的足跡。動態代理技術用起來簡單,但是理解起來并不是那么順暢,我們從最簡單的地方開說。
我們先來看看動態代理的定義:
[Definition]
動態代理類是這樣的一個類:可以在運行時、在創建這個類的時候才指定它所實現的接口。每個代理類的實例都有一個對應的InvocationHandler對象。
也許看完這個定義,第一感覺是“看了還不如不看”J。
不過在你理解了動態代理之后你會體會到這句話的確很精辟。
下面是改自JDK Doc中的一個動態代理的例子,我們先來個感性認識,看例子的時候別忘了回頭復習一下那個Definition,也你靈光一閃,一切都豁然開朗。
/*******************************begin******************************************/
[Demo-1]
//要代理的接口的定義
public interface BusinessIntf {
void doSomething();
}
//用戶代碼
BusinessIntf b = (BusinessIntf)Proxy.newProxyInstance(BusinessIntf.class.getClassLoader ,
new Class[] { BusinessIntf.class },
handler);
b.doSomething();
/*********************************end****************************************/
觀后而感之,使用動態代理就這么簡單。在運行時、在Proxy實例創建時指定要代理的接口(這里的代理接口是BusinessIntf,我們要通過Proxy來獲得該接口的一個實現類的實例)。除了指定代理接口之外,我們不能忘記還有個重要的參數需要傳遞, 那就是一個InvocationHandler接口的實現。大家一定想到了真正的業務邏輯實現一定與handler參數有關,繼續探秘。
察看Doc,發現InvocationHandler下面只有這么一個方法:
Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
Doc中的英文說明太長,不看了,找一個例子看看吧。
/************************************begin************************************/
public class MyInvocationHandler implements InvocationHandler{
private final Object target;
public MyInvocationHandler(final Object target){
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
Object result = method.invoke (target, args);
return result;
} catch (final InvocationTargetException e) {
throw e.getTargetException( );
}
}
};
BusinessImpl target = new BusinessImpl (); //BusinessImpl class implements the BusinessIntf
MyInvocationHandler handler = new MyInvocationHandler (target);
/***********************************end***************************************/
恍然大悟,原來真正實現業務邏輯的是傳給MyInvocationHandler的一個BusinessIntf的實現。
也許你又陷入另一種疑惑當中,你心里可能在想這樣的一個問題:只是為了獲得BusinessIntf的一個實現類的實例而已,用得著使用動態代理這樣高級的技術,繞個大圈子嗎?像下面這樣寫不就可以了么
BusinessIntf b = new BusinessImpl ();
b.doSomething();
或者如果想寫的高級一點我們可以采用靜態代理,使用工廠模式
比如:
class BusinessFactory {
//…
public static BusinessInf getBussinessImpl() {
return new BusinessImpl();
}
}
BusinessIntf b = BusinessFactory.getBussinessImpl();
b.doSomething();
我曾幾何時不是這么想的。不過還是先看看下面的理由能否說服你吧。
[理由1] – 大師言論
《設計模式》一書中給出的理由是“我們有時需要提供一個代理來控制對這個對象(上面例子中的target,BusinessImpl的一個實例)的訪問”。書中列舉了幾種可能使用到代理的情況:
l Remote Proxy – 隱藏對象的空間信息
l Virtual Proxy – 不常見
l Protection Proxy – 訪問權限控制
l Smart Reference – 用于提供訪問對象時的附加操作
[理由2] – 動態性
運行時改變 – 體現出其動態性
之所以稱之為動態代理,就是因為該代理類的實例可實現任意的業務接口,并且可以在運行時決定一個實例究竟實現哪個接口。
從上面的代碼也可以看出:
1、 我們可以在運行時改變我們要實現的接口;
2、 我們可以在運行時改變傳入的InvocationHandler的實現;換句話說InvocationHandler可以創建任何接口的實例;
3、 我們可以改變在MyInvocationHandler中那個真正實現業務邏輯的對象(就是那個target)。
以上的動態性是使用靜態代理較難做到的。
美則觀之,
美則用之
經過上面的闡述,我們領略些動態代理的優勢,不過我們再來看看Demo-1的用戶代碼,
BusinessIntf b = (BusinessIntf)Proxy.newProxyInstance(BusinessIntf.class.getClassLoader ,
new Class[] { BusinessIntf.class },
handler);
b.doSomething();
要使用BusinessIntf接口還真是不那么容易,起碼我們需要自己傳入handler,而handler的定義也給用戶帶來了很大的麻煩。
我們要明確用戶究竟想要什么?
當用戶寫下如下代碼“BusinessIntf b = ”時你會怎么想,顯然用戶需要的是一個BusinessIntf接口實現類的實例。而像上面的代碼我們卻要求用戶寫一些他們并不十分關心的東西,這顯然不美。我們來做一下改進,使動態代理可以像靜態代理那樣用。
[Demo-2]
/*******************************begin******************************************/
public class BusinessProxyFactory {
public static BusinessIntf newProxyInstance() {
BusinessImpl target = new BusinessImpl ();
MyInvocationHandler handler = new MyInvocationHandler (target);
return (BusinessIntf)Proxy.newProxyInstance(BusinessIntf.class.getClassLoader() ,
new Class[] { BusinessIntf.class },
handler);
}
}
//用戶代碼
BusinessIntf b = BusinessProxyFactory.newProxyInstance();
b.doSomething();
/*******************************end******************************************/
輕量級容器之風行
自從PicoContainer、Spring等輕量級容器誕生后,在J2EE世界就刮起了一股“輕量級”之風。輕量級容器實現了一種“依賴注入”的機制。
以PicoContainer為例,它實現了
a) 全權管理組件的創建、生命周期和依賴關系;
b) 使用者獲取組件必須通過容器,容器保證組件全局唯一訪問點。
我在這里對上面的代碼進行“容器化改造”,使之跟上“容器之風”J
// GeneralInvocationHandler.java
public interface GeneralInvocationHandler extends InvocationHandler{
Class getImplClass();
}
//ProxyFactory.java
public class ProxyFactory {
private GeneralInvocationHandler handler;
public ProxyFactory(GeneralInvocationHandler handler){
this.handler = handler;
}
public Object newProxyInstance(){
return Proxy.newProxyInstance(handler.getImplClass().getClassLoader(),
new Class[] { handler.getImplClass() },
handler);
}
}
[Note] Demo-3設計說明(2)
在類圖中BusinessImplProxy實現了GeneralInvocationHandler,并依賴BusinessIntf接口,也就是說一個GeneralInvocationHandler的實現類(如BusinessImplProxy)是與一個特定的業務接口綁定的,它只能代理唯一的接口,不過選擇哪個代理接口的實現類,我們可以在配置文件中在運行時指定。
//BusinessIntf.java,定義一個業務接口
public interface BusinessIntf {
void doSomething();
}
//BusinessImplProxy.java,該類綁定了BusinessIntf接口
public class BusinessImplProxy implements GeneralInvocationHandler{
private BusinessIntf b ;
public BusinessImplProxy(BusinessIntf b){
this.b = b;
}
public Class getImplClass() {
return BusinessIntf.class;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
Object result = method.invoke(b, args);
return result;
} catch (final InvocationTargetException ex) {
throw ex.getTargetException( );
}
}
}
[Note] Demo-3設計說明(3)
經過上面的兩個說明,我們可以得出下面結論:
1、 ProxyFactory可以獲取任意接口的實例,它依賴于一個綁定了特定業務接口的GeneralInvocationHandler的實現類;
2、 GeneralInvocationHandler的實現類綁定了特定的業務接口,我們可以在運行時指定具體的業務接口的實現類;
3、 所有這些我們都使用PicoContainer來進行組裝,我們只需要提供配置文件。
/*******************************begin******************************************/
//Client.java ,欲使用BusinessIntf接口的Client
public class Client {
private ProxyFactory pf;
public Client(ProxyFactory pf){
this.pf = pf;
}
public void run(){
BusinessIntf b = (BusinessIntf)pf.newProxyInstance();
b.doSomething();
}
}
//Main.java
public class
public PicoContainer buildContainer(ScriptedContainerBuilder builder,
PicoContainer parentContainer, Object scope) {
ObjectReference containerRef = new SimpleReference();
ObjectReference parentContainerRef = new SimpleReference();
parentContainerRef.set(parentContainer);
builder.buildContainer(containerRef, parentContainerRef, scope, true);
return (PicoContainer) containerRef.get();
}
public void startup() {
Reader script = null;
try {
script = new FileReader("nanocontainer.xml");
} catch (FileNotFoundException fnfe) {
fnfe.printStackTrace();
}
XMLContainerBuilder builder = new XMLContainerBuilder(script,
getClass().getClassLoader());
PicoContainer pico = buildContainer(builder, null, "SOME_SCOPE");
Client c = (Client)pico.getComponentInstance(Client.class);
c.run();
}
public static void main(String[] args){
Main app = new
app.startup();
}
}
//nanocontainer.xml
<container>
<component-implementation class='ProxyFactory'/>
<component-implementation class='BusinessImpl'/>
<component-implementation class='BusinessImplProxy'/>
<component-implementation class='Client'/>
</container>
/*******************************end******************************************/
進化,go on!, AOP
[Note]
基礎設施、業務組件和用戶代碼三者之間的關系:
l 基礎設施:包括系統的日志、安全性檢查、事務管理等,這些功能的共同點 就是存在于各個業務對象的繼承體系當中,任何業務對象都有可能需要它們。
l 業務組件:系統對外提供核心業務邏輯的業務對象或業務對象的集合。
l 用戶代碼:根據系統提供的業務接口,調用業務組件完成特定功能。
一般用戶代碼只和業務組件打交道,用戶并不關心業務組件是否使用了和使用了哪些基礎設施。在Note中也說過基礎設施存在于各個業務組件中,我們來考慮這樣一個問題:假設我們有業務組件business1,business2,business3,我們提供了日志和事務管理兩種基礎設施,開始的時候我們由于需求的原因,我們只在各個業務組件(business1—business3)中使用了日志這么一種基礎設施,現在需求發生變化了,我們需要在各個組件中加入事務管理。我們怎么辦?體力活,一個組件一個組件的修改。客戶的需求總是在變化,也許明天又會有“添加安全性檢查”的需求。現在一切都集中到了這樣一個問題上:
[問題]
“如何不修改業務組件代碼,而動態的添加和刪除組件需要的基礎設施”?
Interceptor(攔截器),將各個基礎設施都實現為攔截器,業務組件需要哪些基礎設施直接在配置文件中配置即可。而業務組件在真正執行業務前需經過一個基礎設施的攔截器鏈的攔截。而攔截器的一個主要的實現技術就是“動態帶來技術”。當然這個實現更加復雜。
了解AOP的人對上面的描述一定不會感到陌生,因為這也恰是一種AOP的思想。目前很多AOP的開源實現都是基于“動態代理”技術。著名的AOP聯盟也發布了“基于動態代理的AOP框架”。如果對之感興趣的話,可以繼續深入研究。