在OSGi容器外和OSGi進行交互
對于用過equinox的同學,或者看過我之前那兩篇OSGi opendoc附帶的例子的同學而言,都會知道通過命令行來啟動equinox的方式,常見的一種腳本為:java -jar plugins/org.eclipse.osgi_3.2.1.R32x_v20060919.jar -configuration configuration -console,然后在當前目錄的configuration目錄下放置一個config.ini,在此config.ini中通過osgi.bundles=來配置需要加載和啟動的插件,例如osgi.bundles=a.jar@start,那么要在程序中啟動equinox容器,其實基本是差不多的。
查看equinox的代碼,會看到調用上面的org.eclipse.osgi.jar后執行的是EclipeStarter中的靜態run方法,因此只需在外部傳入合適的參數,并調用此run方法即可完成equinox的啟動,在程序中啟動equinox,通常希望做到的是能夠指定config.ini的配置信息以及插件的位置,而不是由equinox去決定,如果不進行設置,默認情況下EclipseStarter將會在工作路徑下產生configuration,并以該configuration目錄下的config.ini作為equinox啟動的配置,對于osgi.bundles配置的bundle的路徑,默認則為當前EclipseStarter代碼所在的目錄,例如上面的命令行,equinox在啟動時就會從plugins目錄中去加載插件,這通常是無法滿足在程序中啟動equinox的需求的,如果想自定義equinox啟動的配置信息,而不是通過去加載指定的configuration中的config.ini,那么可以在程序中調用FrameworkProperties.setProperty來設置啟動equinox的配置信息,如希望指定osgi.bundles中指定的加載的bundle的相對路徑,那么可以在equinox啟動的配置信息中增加osgi.syspath的指定,FrameworkProperties.setProperty("osgi.syspath",你希望指定的bundle所在的路徑),equinox啟動的配置信息還有很多種,具體有需要的話可以查看EclipseStarter中processCommandLine的方法,通過這樣的方式后,就可以采用類似這樣的方式來啟動equinox:EclipseStarter.run(new String[]{"-console"},null);按照上面這樣的方式就可以實現在外部程序中啟動equinox了。
OSGi通過BundleContext來獲取OSGi服務,因此想在OSGi容器外獲取OSGi服務,首要的問題就是要先在OSGi容器外獲取到BundleContext,EclipseStarter中提供了一個getSystemBundleContext的方法,通過這個方法可以輕松的拿到BundleContext,而通過BundleContext則可以容易的拿到OSGi服務的實例,不過這個時候要注意的是,如果想執行這個OSGi服務實例的方法的話,還是不太好做的,因為容器外的classloader和OSGi服務實例的class所在的classloader并不相同,因此不太好按照java對象的方式直接去調用,更靠譜的是通過反射去調用。
如果想在容器外獲取到OSGi容器里插件的class,一個可選的做法是通過BundleContext獲取到Bundle,然后通過Bundle來加載class,采用這樣的方法加載的class就可以保證其是相同的,否則會出現容器外的一個A.class會不等于容器里插件的A.class,這個原因對于稍微知道java classloader機制的人都理解的。
按照上面的說法,一個簡單的啟動Equinox以及與OSGi容器交互的類可以這么寫:
* 啟動并運行equinox容器
*/
public static void start() throws Exception{
// 根據需要加載的bundle組裝出類似a.jar@start,b.jar@3:start這樣格式的osgibundles字符串來
String osgiBundles="";
// 配置Equinox的啟動
FrameworkProperties.setProperty("osgi.noShutdown", "true");
FrameworkProperties.setProperty("eclipse.ignoreApp", "true");
FrameworkProperties.setProperty("osgi.bundles.defaultStartLevel", "4");
FrameworkProperties.setProperty("osgi.bundles", osgiBundlesBuilder.toString());
// 根據需要設置bundle所在的路徑
String bundlePath="";
// 指定需要加載的plugins所在的目錄
FrameworkProperties.setProperty("osgi.syspath", bundlePath);
// 調用EclipseStarter,完成容器的啟動,指定configuration目錄
EclipseStarter.run(new String[]{"-configuration","configuration","-console"}, null);
// 通過EclipeStarter獲取到BundleContext
context=EclipseStarter.getSystemBundleContext();
}
/**
* 停止equinox容器
*/
public static void stop(){
try {
EclipseStarter.shutdown();
context=null;
}
catch (Exception e) {
System.err.println("停止equinox容器時出現錯誤:"+e);
e.printStackTrace();
}
}
/**
* 從equinox容器中獲取OSGi服務instance 還可以基于此進一步處理多服務接口實現的狀況
*
* @param serviceName 服務名稱(完整接口類名)
*
* @return Object 當找不到對應的服務時返回null
*/
public static Object getOSGiService(String serviceName){
ServiceReference serviceRef=context.getServiceReference(serviceName);
if(serviceRef==null)
return null;
return context.getService(serviceRef);
}
/**
* 獲取OSGi容器中插件的類
*/
public static Class<?> getBundleClass(String bundleName,String className) throws Exception{
Bundle[] bundles=context.getBundles();
for (int i = 0; i < bundles.length; i++) {
if(bundleName.equalsIgnoreCase(bundles[i].getSymbolicName())){
return bundles[i].loadClass(className);
}
}
}
在實現了OSGi容器外與OSGi交互之后,通常會同時產生一個需求,就是在OSGi容器內的插件要加載OSGi容器外的類,例如OSGi容器內提供了一個mvc框架,而Action類則在OSGi容器外由其他的容器負責加載,那么這個時候就會產生這個需求了,為了做到這點,有一個比較簡單的解決方法,就是編寫一個Bundle,在該Bundle中放置一個允許設置外部ClassLoader的OSGi服務,例如:
public class ClassLoaderService{
public void setClassLoader(ClassLoader classloader);
}
然后基于上面的方法,在外部啟動equinox的類中去反射執行ClassLoaderService這個OSGi服務的setClassLoader方法,將外部的classloader設置進來,然后在OSGi容器的插件中需要加載OSGi容器外的類的時候就調用下這個ClassLoaderService去完成類的加載。
基于以上說的這些方法,基本上是可以較好的實現OSGi容器與其他容器的結合,例如在tomcat中啟動OSGi等,不過在動態化這塊就沒有那么好處理了,除非外部的容器也能提供相應的動態的機制,具體在下一篇的blog中再來細致的討論下OSGi的動態化。
posted on 2009-04-24 21:10 BlueDavy 閱讀(7079) 評論(11) 編輯 收藏 所屬分類: OSGi、SOA、SCA