天天看點

Eclipse RCP使用Spring時遇到的問題及解決過程

需求環境:

公司的JinbuBox用戶端采用Eclipse RCP開發,整個工程隻有一個Bundle, 包含所有的jar、資源等等,工程使用了Spring;

問題:在用戶端需要更新時,可以采用Eclipse RCP的bundle更新機制。但是因為整個工程隻有一個Bundle,是以如果更新就需要将整個應用全部更新一遍,而事實上其核心變動很小(自己的業務Class、及部分圖檔等資源)。是以,需要将工程拆分為不同的Bundle.

解決方案:

考慮到工程中的jar檔案比較多,體積最大,是以将所有的jar作為一個獨立的Bundle. 這個Eclipse有很好的解決方案,New Project→Plugin Development Project→plug-in from exsiting archieves,将所有的jar導入即可制作一個獨立的Bundle:sharedjars。然後将所有的package都Export出去(注意,export是預設的行為,但是可能是因為package太多,預設的export行為會漏掉很多package,是以這裡需要手動确認是否所有的package都被exported了)

因為需要使用Spring, 考慮到Osgi的classloader機制,将所有Spring的jar檔案保留在原來的Bundle中,這樣主Bundle通過required bundle引入sharedjars bundle,在runtime中設定spring.jar。

問題:

1、在運作時,多次提示說某某bundle已經被引入了,比如(javax.servlet.**)等等。這個主要是因為在Target platform中選的plug-ins有重複,隻要将對應的取消就可以了;

2、Spring啟動時,一些Bean不能建立

剛開始時以為是Spring即時加載某些bean時相關的Bean還沒有建構,是以設定lazy-init為true,這樣啟動就沒有問題了。

但是在運作時,卻發現所有用戶端的Spring Bean都不能通路,表象就是Spring的DI沒有工作。

解決過程:

經過調試,發現在調用Spring Bean的地方,Thread.currentThread.getContextClassLoader()以及其上級ClassLoader中都沒有加載Spring的Bean. 但是在啟動時,加載Spring Context時是正常加載的,不過其ClassLoader與前面的ClassLoader是兩個無相交節點的樹。

奇怪了! 是,沒錯,OSGi的不同Bundle确實是不同的ClassLoader,但是我已經把所有Spring相關的代碼、jar全部放到同一個Bundle中了呀?而且隻有一個Bundle時,系統運作是沒有問題的。這就說明這麼引入Spring是沒有問題的(Spring DM是不同Bundle之間将Spring的Bean作為Service互相引用,與這個需求沒有太大關系)

嘗試着通過其他方式擷取Spring Bean,比如BeanLocator.getBean, 經過驗證,這種方式是可以正常工作的。 一個苦工的做法會是将代碼中所有XXXServiceUtil.xxxx的靜态方法調用service都修改為先擷取Spring Bean,然後再轉型為對應的Service執行個體,然後再調用。但是,這不是根本辦法。

又仔細想了想,在RCP應用中使用了Spring Http Invoker, 這是一種通過Proxy模式來通路伺服器端Service的方法。 記得在Spring的AOP Proxy相關文檔中曾經提過,AOP Proxy有兩種方式,一種是JDK Proxy, 另外一種是CGLIB Proxy(when business object doesn't imlement an interface)。而在我的bundle中,将cglib.jar放到了另外的Bundle中,這樣初始化Spring時,會通過Cglib的ClassLoader去加載Spring Bean Class,而因為Cglib放在Spring所在Bundle之外的bundle中,是以Spring的Bean就沒有被正确地初始化。

趕緊試了一下,将sharedjars bundle中的cglib相關代碼全部删掉,然後在主Bundle的runtime classpath中,把Cglib.jar加上。

運作,一切OK!

繼續閱讀