領先一步
VMware 提供培訓和認證,以加速您的進展。
了解更多在最近的一篇部落格文章中,Marc Logemann 探討了代理效能的主題。在他的文章中,他要求「Spring 團隊」提供一份白皮書。我不想花費 (頁) 又 (頁) 的篇幅來討論代理和位元組碼編織機制之間奈秒級的差異,但我確實認為再次重申這些差異是什麼,以及這個討論是否重要是有價值的。
換句話說,代理可以用作實際對象的替身,以便將額外的行為應用於這些對象——無論是與安全性相關的行為、快取,還是效能測量。
許多現代框架使用代理來實現原本不可能實現的功能。許多物件關聯對應器使用代理來實現防止在實際需要資料之前載入資料的行為(這有時稱為延遲載入)。Spring 也使用代理來實現其某些功能,例如其遠端處理工具、其交易管理工具和 AOP 框架。
代理的替代方案是位元組碼編織。當使用位元組碼編織機制時,永遠不會有第二個物件(又名代理)。相反,如果需要應用行為(例如交易管理或安全性),它會被編織「到」現有程式碼中,而不是「圍繞它」。執行編織過程的一種方法是使用 Java 5 的 -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 等人比我更擅長這方面,所以不用擔心 ;-)。