領先一步
VMware 提供培訓和認證,助您加速進步。
瞭解更多在最近的一篇部落格文章中,Marc Logemann 談到了代理效能的主題。在他的文章中,他要求“Spring 開發者”提供一份白皮書。我不想花費大量篇幅討論代理和位元組碼織入機制之間精確到納秒的差異,但我確實認為重申這些差異以及這種討論是否重要是有價值的。

換句話說,代理可以作為真實物件的替身,為這些物件應用額外的行為——無論是安全相關的行為、快取還是效能測量。
許多現代框架使用代理來實現原本不可能實現的功能。許多物件關係對映器使用代理來實現一種行為,即資料直到真正需要時才載入(這有時被稱為懶載入)。Spring也使用代理來實現其某些功能,例如遠端呼叫設施、事務管理設施和AOP框架。
代理的替代方案是位元組碼織入。當使用位元組碼織入機制時,不會有第二個物件(即代理)。相反,如果需要應用行為(例如事務管理或安全),它會被“織入”到現有程式碼中,而不是“圍繞”現有程式碼。執行織入過程的一種方法是使用Java5的-javaagent標誌。還有其他方法可用。
換句話說:使用代理,最終會得到一個位於目標物件前面的代理物件,而使用位元組碼織入方法,則不會有必須委託呼叫的代理。
請注意,我不會在此提供具體數字。正如Stefan Hansel在他對Marc部落格的評論中正確指出的,衡量普通目標呼叫與中間有代理之間的差異的微基準測試(或任何微基準測試)並沒有真正的意義,因為還有一大堆其他因素需要考慮。
如果我在我的筆記型電腦(MacBook)上使用普通的JDK動態代理(稍後會詳細介紹)執行此程式碼,那麼對myRealObject的一次方法呼叫需要9納秒(10-9)。對代理物件的一次呼叫需要500納秒(大約慢50倍)。
// real object
MyInterface myRealObject;
myRealObject.doIt();
// proxied object
MyInterface myProxiedObject;
myProxiedObject.doIt();
相比之下,如果我使用位元組碼織入方法(在這種情況下,我使用AspectJ來模擬相同的設定),我的呼叫只增加了大約2納秒。
因此,總結來說,我無法改變事實:代理會給普通方法呼叫帶來顯著的開銷。
在我們繼續之前,首先要認識到這裡增加的開銷是固定的。如果doIt()方法本身需要5秒,被代理的呼叫絕不會需要50倍的時間。不,相反,呼叫將需要5秒 + 約500納秒。
我們使用代理來透明地為物件新增行為。也許是為了用安全規則裝飾一個物件(管理員可以訪問它,但普通使用者不能),或者是因為我們想要啟用懶載入,只在第一次訪問時從資料庫載入資料。另一個原因可能是為我們的物件啟用透明的事務管理。

服務本身的呼叫現在肯定會涉及一定的開銷(我們之前已經討論過的開銷)。然而問題是,我們用這些開銷換來了什麼?
程式碼簡化 我們透過在中間放置一個代理大大簡化了程式碼。如果我們使用Spring提供的@Transactional註解,我們只需要做以下事情
public class Service {
@Transactional
public void executeService() { }
}
還是
<tx:annotation-driven/>
<bean class="com.mycompany.Service"/>
一種替代的(程式設計的)方法將涉及顯著修改客戶端(呼叫者)或服務類本身。
集中式事務管理 事務管理現在由一箇中央設施負責,允許進行更多的最佳化和一種非常一致的事務管理方法。如果我們自己在服務或呼叫者中實現事務管理程式碼,這是不可能實現的。
public Object invoke(Object proxy, Method proxyMethod, Object[] args)
throws Throwable {
Method targetMethod = null;
if (!cachedMethodMap.containsKey(proxyMethod)) {
targetMethod = target.getClass().getMethod(proxyMethod.getName(),
proxyMethod.getParameterTypes());
cachedMethodMap.put(proxyMethod, targetMethod);
} else {
targetMethod = cachedMethodMap.get(proxyMethod);
}
Ojbect retVal = targetMethod.invoke(target, args);
return retVal;
}
public Object invoke(Object proxy, Method proxyMethod, Object[] args)
throws Throwable {
Method targetMethod = target.getClass().getMethod(proxyMethod.getName(),
proxyMethod.getParameterTypes());
Ojbect retVal = targetMethod.invoke(target, args);
return retVal;
}
換句話說,將生成或建立代理的工作留給瞭解自己在做什麼的人或框架。幸運的是,我沒有參與代理設計,Rob、Juergen、Rod等人在這方面比我強得多,所以不用擔心 ;-)。