OSGi原理与最佳实践:第一章 OSGi框架简介(3)

 由  ValRay 发布

1.1.4.2 Standalone的应用

我们的第一个 HelloWorld 的例子,就是一个 Standalone 类型的应用。当然 HelloWorld 中只有一个 Bundle,而实际的例子会由很多的 Bundle 组成,我们可以使用经典的 BundleActivator 注册服务,或者用 Declarative Service 来注入组件的方式完成功能。

1.1.4.3 C/S

C/S 结构的程序可以看成是两个 Standalone 的应用。它们遵循一定的协议进行通信。所以 C/S 程序的开发对我们来说也不存在什么困难。

1.1.4.4 嵌入式

提到嵌入式结构的系统开发,绝对是 OSGi 的强项,OSGi 的诞生就是为嵌入式系统提供支撑的,所以在嵌入式系统方面采用 OSGi 没有任何的问题。综合对于 B/S、Standalone、C/S、嵌入式这几种体系结构系统的支持,可以看出对于 Standalone、C/S、嵌入式系统而言采用 OSGi 框架没有什么太多的问题,对于 B/S 结构的系统而言其实大部分情况下是可以通过采用 HttpService 来实现的,实在不行的话可以采用 Equinox 提供的 server-side incubator 中的 bridge.war 来实现,相信随着 OSGi 在 Server-Side 应用和企业应用中的不断发展,对于 B/S 结构应用的支撑将会越来越优秀。

1.1.4.5 Equinox注意事项

在使用 Equinox 时,特别要注意的是使用 classloader 去加载资源文件的时候,由于每个 Bundle 拥有独立的 classloader。而有些开源框架会使用顶级 classloader 去加载文件,这个时候会导致在 Equinox 中运行时出错。如在 equinox 使用 spring 时,若配置文件中采用了 classpath:file 这样的方式去加载其他的配置文件,就会出现找不到文件的现象,这个错误就是由这个问题引起的。

另外要注意的就是包的命名问题,如果引用了其他 Bundle Export 的 package,那么在当前 Bundle 中就不能再建同样的包了,否则在运行时会出现找不到包中类的现象。

1.1.5 从外部启动Equinox

前面,我们都是通过 Eclipse 启动我们的 Bundle。但是,在有些时候,我们希望自己来控制 OSGi 的容器的启动,并且在 OSGi 的容器外部获取 OSGi 的服务,甚至是把 OSGi 的容器内嵌到我们的应用之中。下面我们就来看一下如何把 Equinox 嵌入到应用中。由应用来启动 Equinox、获取 OSGi 的服务,以及加载 OSGi 容器中的其他插件的类。并且也会演示 OSGi 容器中的插件如何加载 OSGi 容器外的类的方法。

我们在前面演示了如何通过命令行来启动 Equinox ,常见的一种脚本为: java-jar plugins/org.eclipse.osgi3.4.3.R34xv20081215-1030.jar -configuration configuration -console,然后在当前的 configuration 目录下放置一个 config.ini,在此 config.ini 中通过 osgi.bundles=来配置要加载和启动的插件,例如 osgi.bundles=example.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 中提供了一个 getSystemBundle- Context 的方法,通过这个方法可以轻松拿到 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 等,或者在我们自身的应用中来控制 OSGi 的容器。到这里,我们基本上完成了对于 Equinox 的介绍,下面来看另外一个应用也较广泛的 OSGi 的容器——Felix。

查看评论