在使用OSGi時(shí),有些時(shí)候會(huì)需要在OSGi容器外獲取OSGi服務(wù),加載OSGi容器加載的class,或者說(shuō)需要內(nèi)嵌OSGi容器,本篇blog以一個(gè)簡(jiǎn)單的例子來(lái)說(shuō)明如何基于equinox實(shí)現(xiàn)OSGi容器的內(nèi)嵌,或者說(shuō)通過(guò)程序來(lái)啟動(dòng)equinox,同時(shí)也通過(guò)此例子展示下如何在容器外來(lái)獲取OSGi服務(wù)以及加載OSGi容器里面其他插件的class,同時(shí)還會(huì)附送一個(gè)如何讓OSGi容器里的插件能加載到OSGi容器外的類(lèi)的方法。
對(duì)于用過(guò)equinox的同學(xué),或者看過(guò)我之前那兩篇OSGi opendoc附帶的例子的同學(xué)而言,都會(huì)知道通過(guò)命令行來(lái)啟動(dòng)equinox的方式,常見(jiàn)的一種腳本為:java -jar plugins/org.eclipse.osgi_3.2.1.R32x_v20060919.jar -configuration configuration -console,然后在當(dāng)前目錄的configuration目錄下放置一個(gè)config.ini,在此config.ini中通過(guò)osgi.bundles=來(lái)配置需要加載和啟動(dòng)的插件,例如osgi.bundles=a.jar@start,那么要在程序中啟動(dòng)equinox容器,其實(shí)基本是差不多的。
查看equinox的代碼,會(huì)看到調(diào)用上面的org.eclipse.osgi.jar后執(zhí)行的是EclipeStarter中的靜態(tài)run方法,因此只需在外部傳入合適的參數(shù),并調(diào)用此run方法即可完成equinox的啟動(dòng),在程序中啟動(dòng)equinox,通常希望做到的是能夠指定config.ini的配置信息以及插件的位置,而不是由equinox去決定,如果不進(jìn)行設(shè)置,默認(rèn)情況下EclipseStarter將會(huì)在工作路徑下產(chǎn)生configuration,并以該configuration目錄下的config.ini作為equinox啟動(dòng)的配置,對(duì)于osgi.bundles配置的bundle的路徑,默認(rèn)則為當(dāng)前EclipseStarter代碼所在的目錄,例如上面的命令行,equinox在啟動(dòng)時(shí)就會(huì)從plugins目錄中去加載插件,這通常是無(wú)法滿(mǎn)足在程序中啟動(dòng)equinox的需求的,如果想自定義equinox啟動(dòng)的配置信息,而不是通過(guò)去加載指定的configuration中的config.ini,那么可以在程序中調(diào)用FrameworkProperties.setProperty來(lái)設(shè)置啟動(dòng)equinox的配置信息,如希望指定osgi.bundles中指定的加載的bundle的相對(duì)路徑,那么可以在equinox啟動(dòng)的配置信息中增加osgi.syspath的指定,F(xiàn)rameworkProperties.setProperty("osgi.syspath",你希望指定的bundle所在的路徑),equinox啟動(dòng)的配置信息還有很多種,具體有需要的話(huà)可以查看EclipseStarter中processCommandLine的方法,通過(guò)這樣的方式后,就可以采用類(lèi)似這樣的方式來(lái)啟動(dòng)equinox:EclipseStarter.run(new String[]{"-console"},null);按照上面這樣的方式就可以實(shí)現(xiàn)在外部程序中啟動(dòng)equinox了。
OSGi通過(guò)BundleContext來(lái)獲取OSGi服務(wù),因此想在OSGi容器外獲取OSGi服務(wù),首要的問(wèn)題就是要先在OSGi容器外獲取到BundleContext,EclipseStarter中提供了一個(gè)getSystemBundleContext的方法,通過(guò)這個(gè)方法可以輕松的拿到BundleContext,而通過(guò)BundleContext則可以容易的拿到OSGi服務(wù)的實(shí)例,不過(guò)這個(gè)時(shí)候要注意的是,如果想執(zhí)行這個(gè)OSGi服務(wù)實(shí)例的方法的話(huà),還是不太好做的,因?yàn)槿萜魍獾腸lassloader和OSGi服務(wù)實(shí)例的class所在的classloader并不相同,因此不太好按照java對(duì)象的方式直接去調(diào)用,更靠譜的是通過(guò)反射去調(diào)用。
如果想在容器外獲取到OSGi容器里插件的class,一個(gè)可選的做法是通過(guò)BundleContext獲取到Bundle,然后通過(guò)Bundle來(lái)加載class,采用這樣的方法加載的class就可以保證其是相同的,否則會(huì)出現(xiàn)容器外的一個(gè)A.class會(huì)不等于容器里插件的A.class,這個(gè)原因?qū)τ谏晕⒅纉ava classloader機(jī)制的人都理解的。
按照上面的說(shuō)法,一個(gè)簡(jiǎn)單的啟動(dòng)Equinox以及與OSGi容器交互的類(lèi)可以這么寫(xiě):
/**
* 啟動(dòng)并運(yùn)行equinox容器
*/
public static void start() throws Exception{
// 根據(jù)需要加載的bundle組裝出類(lèi)似a.jar@start,b.jar@3:start這樣格式的osgibundles字符串來(lái)
String osgiBundles="";
// 配置Equinox的啟動(dòng)
FrameworkProperties.setProperty("osgi.noShutdown", "true");
FrameworkProperties.setProperty("eclipse.ignoreApp", "true");
FrameworkProperties.setProperty("osgi.bundles.defaultStartLevel", "4");
FrameworkProperties.setProperty("osgi.bundles", osgiBundlesBuilder.toString());
// 根據(jù)需要設(shè)置bundle所在的路徑
String bundlePath="";
// 指定需要加載的plugins所在的目錄
FrameworkProperties.setProperty("osgi.syspath", bundlePath);
// 調(diào)用EclipseStarter,完成容器的啟動(dòng),指定configuration目錄
EclipseStarter.run(new String[]{"-configuration","configuration","-console"}, null);
// 通過(guò)EclipeStarter獲取到BundleContext
context=EclipseStarter.getSystemBundleContext();
}
/**
* 停止equinox容器
*/
public static void stop(){
try {
EclipseStarter.shutdown();
context=null;
}
catch (Exception e) {
System.err.println("停止equinox容器時(shí)出現(xiàn)錯(cuò)誤:"+e);
e.printStackTrace();
}
}
/**
* 從equinox容器中獲取OSGi服務(wù)instance 還可以基于此進(jìn)一步處理多服務(wù)接口實(shí)現(xiàn)的狀況
*
* @param serviceName 服務(wù)名稱(chēng)(完整接口類(lèi)名)
*
* @return Object 當(dāng)找不到對(duì)應(yīng)的服務(wù)時(shí)返回null
*/
public static Object getOSGiService(String serviceName){
ServiceReference serviceRef=context.getServiceReference(serviceName);
if(serviceRef==null)
return null;
return context.getService(serviceRef);
}
/**
* 獲取OSGi容器中插件的類(lèi)
*/
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);
}
}
}
在實(shí)現(xiàn)了OSGi容器外與OSGi交互之后,通常會(huì)同時(shí)產(chǎn)生一個(gè)需求,就是在OSGi容器內(nèi)的插件要加載OSGi容器外的類(lèi),例如OSGi容器內(nèi)提供了一個(gè)mvc框架,而Action類(lèi)則在OSGi容器外由其他的容器負(fù)責(zé)加載,那么這個(gè)時(shí)候就會(huì)產(chǎn)生這個(gè)需求了,為了做到這點(diǎn),有一個(gè)比較簡(jiǎn)單的解決方法,就是編寫(xiě)一個(gè)Bundle,在該Bundle中放置一個(gè)允許設(shè)置外部ClassLoader的OSGi服務(wù),例如:
public class ClassLoaderService{
public void setClassLoader(ClassLoader classloader);
}
然后基于上面的方法,在外部啟動(dòng)equinox的類(lèi)中去反射執(zhí)行ClassLoaderService這個(gè)OSGi服務(wù)的setClassLoader方法,將外部的classloader設(shè)置進(jìn)來(lái),然后在OSGi容器的插件中需要加載OSGi容器外的類(lèi)的時(shí)候就調(diào)用下這個(gè)ClassLoaderService去完成類(lèi)的加載。
基于以上說(shuō)的這些方法,基本上是可以較好的實(shí)現(xiàn)OSGi容器與其他容器的結(jié)合,例如在tomcat中啟動(dòng)OSGi等,不過(guò)在動(dòng)態(tài)化這塊就沒(méi)有那么好處理了,除非外部的容器也能提供相應(yīng)的動(dòng)態(tài)的機(jī)制,具體在下一篇的blog中再來(lái)細(xì)致的討論下OSGi的動(dòng)態(tài)化。