領先一步
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 等人在這方面比我做得好得多,所以不用擔心 ;-)。