Spring与OSGi的整合

 

1. 开发环境的准备

现在的eclipse都已经包含了Equinox,无需单独下载。
下载最新版的Spring DM,Spring官方网站:www.SpringSource.org
2. 开发OSGi的HelloWorld应用程序

在这一节,我们将开发一个OSGi bundle,演示如何利用Equinox进行OSGi bundle的开发、运行及调试,为之后的示例做准备。
首先,新建一个Plug-in工程

下一步,注意选择目标平台,默认为Eclipse version *,将其改成Equinox,

之后按默认下一步即可,到最后一步时,无需根据模板创建工程,去掉默认的勾,点击完成

可以看到,eclipse为工程自动生成了一个Activator类,如下所示

 

package helloworld;

import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;

public class Activator implements BundleActivator {
	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext
	 * )
	 */
	public void start(BundleContext context) throws Exception {
		System.out.println("Hello World!");
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext)
	 */
	public void stop(BundleContext context) throws Exception {
	}
}

 有过eclipse插件开发经验的人应该很快就能明白,这就是该工程——实际上就是上一篇文章中所称的bundle在Equinox平台中的启动入口,相当于我们的熟悉的main函数,如果将该bundle运行到Equinox中,首先进入该bundle的是Activator的start方法,你可以将相关的服务、资源在该方法内完成向bundleContext的注册——文章后面会具体讲到相关内容。在运行期,如果想要该bundle停止运行,Equinox平台将调用Activator的stop方法,你可以在该方法内完成资源的注销等工作。

 

下面我们将要完成首次的Equinox运行,将向大家展现bundle到底是如何运行起来的。为helloworld选择运行方式

弹出对话框

 

在弹出的对话框中,新建一个OSGi Framework运行环境(双击OSGi Framework即可,这里为其取名Equinox),选中helloworld(1.0.0),然后点击右边的Add Required Bundles按钮,eclipse将自动选中运行helloworld的依赖bundle,如果有必要,可以点击右下的Validate Bundles验证按钮,验证程序正常运行所需的bundle是否都被选中,最后点击运行,回到控制台:
osgi> Hello World!
可以看到,之前在Activator的start方法中的输出语句已经被输出到控制台,我们可以通过命令ss查看Equinox的运行情况,可以看到一共运行了两个bundle,如下:
ss

Framework is launched.

id State Bundle
0 ACTIVE org.eclipse.osgi_3.4.3.R34x_v20081215-1030
1 ACTIVE helloworld_1.0.0
还记得之前讲到过的bundle的几种状态吧?helloworld已经运行起来了。
那么Equinox具体支持哪些命令呢?下表列出了主要的一些命令,如需查看更详细的命令清单,则可以在控制台键入help。

类别

命令

含义

控制框架

launch

启动框架

shutdown

停止框架

close

关闭、退出框架

exit

立即退出,相当于 System.exit

init

卸载所有 bundle(前提是已经shutdown)

setprop

设置属性,在运行时进行

控制 bundle

install

安装

uninstall

卸载

start

启动

stop

停止

refresh

刷新

update

更新

展示状态

status

展示安装的 bundle 和注册的服务

ss

展示所有 bundle 的简单状态

services

展示注册服务的详细信息

packages

展示导入、导出包的状态

bundles

展示所有已经安装的 bundles 的状态

headers

展示 bundles 的头信息,即MANIFEST.MF 中的内容

log

展示 LOG 入口信息

其它

exec

在另外一个进程中执行一个命令(阻塞状态)

fork

和 EXEC 不同的是不会引起阻塞

gc

促使垃圾回收

getprop

得到属性,或者某个属性

控制启动级别

sl

得到某个 bundle 或者整个框架的start level 信息

setfwsl

设置框架的 start level

setbsl

设置 bundle 的 start level

setibsl

设置初始化 bundle 的 start level

 

至此,我们已经成功的演示了helloworld,初步了解了OSGi的bundle是如何开发并运行的,下面将进入我们的正题,下面我们将通过一个稍微复杂的示例,讲解bundle之间如何进行包依赖、注册及调用服务

 

 

 

3. 开发一组计算器bundle实例

 

本节讲到的例子是仿照网上甚为流行的一个例子,但苦于一直未找到源码,网上贴的都是一些转帖,代码片段,估计初学者很难将其还原并调通!我最开始弄这个咚咚的时候,其过程之痛苦,难以言喻,所以想着仿照该例子的设计,给予实现,文后贴出源码,希望能帮到大家。
该例子是一个关于计算器的实例,osgi.example.compute bundle(下文简称compute bundle)提供了统一的计算接口:Compute,另外两个bundle分别为osgi.example.compute.add(下文简称add bundle)和osgi.example.compute.multiply(下文简称multiply bundle),在这两个bundle中,各自对compute bundle进行不同的实现,一个实现加法,一个实现乘法。另外还有一个服务消费者osgi.example.compute.consumer bundle(下文简称consumer bundle),consumer bundle负责消费add bundle和multiply bundle提供的服务。上述4个bundle之间的关系

 

创建4个bundle之后的工程目录

 

通过该示例,将演示如何利用Spring DM发布和调用OSGi服务,同时还将演示OSGi的动态服务调用能力。

 

 

3.1. bundle osgi.example.compute

 

compute bundle只提供一个接口——Compute,因此无需依赖更多的bundle,只需最基本的osgi即可。因为不涉及注册资源之类的,所以也无需Activator入口类,这个例子的四个bundles都没用用到Activator接口,实现Acitivator接口的类应该是这个bundle的开启时的入口类,而这里没用用到它,这四个bundle同样可以开启成active状态。
Computer接口源代码如下所示:package osgi.example.compute;

public interface Compute {
        public String computeNums(int x, int y);
}

 3.2. bundle osgi.example.compute.add

 

 

add bundle是对compute bundle的具体服务实现,在MANIFEST.MF文件需要引入osgi.example.compute包;当然也可以通过添加依赖bundle的形式,即不引入包,而直接在Required Plug-ins中添加compute bundle。如下图所示,可以看到有Required Plug-ins和Imported Packages两种方式,用Import Packages可能会好一点,至少轻装一点

注意:OSGi官方指出,当需要用到其他bundle的类型时,不提倡依赖bundle,应该尽可能采用Import-package的方式引入包,因为依赖bundle可能在加载bundle的时候发生问题。

通过引入osgi.example.compute包,osgi.example.compute bundle被加到了add bundl的classpath当中,解决了开发时期的类型识别问题。
这样一来,在add bundle中就能使用compute bundle中的接口了,Computer接口的实现如下:

package osgi.example.compute.add;
import osgi.example.compute.Compute;
public class Add implements Compute {
public String computeNums(int x, int y) {
int s = x + y;
String result = "The Sum is---" + String.valueOf(s);
return result;
}
}

 Compute的实现已经实现了,那么如何将其发布出去呢?这个是由Spring DM负责,Spring DM利用OSGi命名空间下的<service>元素将bean导出为OSGi服务。最简单的形式为:

<beans:bean id="beanToPublish" class="com.xyz.imp.MessageServiceImp"/>
<service ref="beanToPublish" interface="com.xyz.MessageService"/>
 从示例中可以看出,beanToPublish被service元素声明导出。

另外,service结点还有一些高级属性,如depends-on、context-class-loader、ranking等待,详情请看spring dm reference。
首先,需要在add bundle的工程根目录下的”META-INF”的文件夹下创建一个文件夹,取名”spring”,Spring DM能够自动解析该文件夹下所有的spring配置文件。spring配置文件的具体内容如下所示:<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:osgi="http://www.springframework.org/schema/osgi"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/osgi http://www.springframework.org/schema/osgi/spring-osgi.xsd">
<bean id="addOsgiService" class="osgi.example.compute.add.Add">
</bean>
<osgi:service id="addService" ref="addOsgiService"
interface="osgi.example.compute.Compute">
</osgi:service>
</beans>

 如此一来,其他bundle就能通过spring dm引入接口类型为osgi.example.compute.Compute的服务了,这里的osgi:service标签的id就是服务的名称,就是将bean转为服务的调用时的名称,ref为引用的是哪个bean,而interface指明只要是这个接口的定义处,都可能会用到这个服务,说可能,是因为一个接口的多个实现,也就是有多样的服务来实现,那调用时就取决于spring dm了,spring dm将通过一定的服务查找策略,返回匹配的服务。

 

3.3. bundle osgi.example.compute.multiply

 

该bundle和add bundle相似,在这就不赘述了。

 

3.4. bundle osgi.example.compute.client

 

顾名思义,该bundle将作为add 、multiply两个bundle的客户bundle,演示如何导入服务。
OSGi的测试工作比较麻烦,这方面还没研究,在这里利用spring实例化bean的时期,从构造函数入手,对服务进行测试。Client类的实现很简单,如下所示:

package osgi.example.client;
import osgi.example.compute.Compute;
public class Client {
/**
* 为了方便测试,采用Spring的构造注入方式,直接在构造函数中调用Compute服务
* @param compute
*/
public Client(Compute compute){
System.out.println(compute.computeNums(5, 6));
}
}
 另外,因为client用到了其他几个bundle的类型,所以需要导入相应的包,步骤在3.2一节已有讲到。

spring dm靠<reference>元素来引入服务,最简单的形式如下所示:

<reference id="beanToPublish" interface="com.xyz.MessageService"/>
   

  如果需要用到该服务,如某个bean包含一个com.xyz.MessageService属性,则配置该bean如下所示

<bean id="referenceBean" class="com.nci.ReferenceBean">
<property name="messageService" ref="beanToPublish"/>
</bean>
  reference元素还有一些高级属性,详情请见spring dm reference。

看一下client的spring配置文件,这里用osgi:reference标签来引用服务,上面的配置中用osgi:service标签来发布服务

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:osgi="http://www.springframework.org/schema/osgi"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/osgi http://www.springframework.org/schema/osgi/spring-osgi.xsd">
<bean id="OSGiClient" class="osgi.example.client.Client">
<constructor-arg ref="ComputeService">
</constructor-arg>
</bean>
<osgi:reference id="ComputeService" interface="osgi.example.compute.Compute" cardinality="1..1">
</osgi:reference>
</beans>
  从上面的示例,我们可以发现,服务的导出的时候都是基于接口的,服务的引用也是基于接口的,不过spring dm支持基于类的导出、导入,但是还是建议尽量基于接口,应该记住面向接口编程的思想,以应对将来有可能发生的改变。

 

 

3.5. 运行

 

由于add和multiply都是基于Compute接口对外导出服务,那么Client到底导入的是哪个服务呢?默认情况下,会导入启动较早的bundle服务(OSGi在bundle启动时,会为其分配一个ID值,启动越早,该值越小)。
运行之前,我们需要做这么一件事,搭建Spring-DM的运行环境:

(1)先到http://www.springsource.org/osgi上去下载Spring DM:spring-osgi-1.2.1-with-dependencies.zip, 解压后有dist和lib两个文件夹里有Spring DM运行的jar包。

(2)在 Package Explorer 上右击,然后点击Import --> Plug-in Development --> Plug-ins and Fragments,然后单击下一步,将弹出Import Plug-ins and Fragments 对话框,选择Directory,然后加到spring-osgi-1.2.1-with-dependencies.zip解压后的文件夹,并进入dist目录,然后点击next,将以下三个插件添加到你的“Plug-ins and Fragments to import”中:

 

以下是引用片段:
org.springframeork.osgi.bundle.core 
org.springframeork.osgi.bundle.extender 
org.springframeork.osgi.bundle.io

现在单击完成。Eclipse会将这三个套件导入到你的工作空间中,在那里你应能够在Package Explorer视图中看到它们。

(3)再将spring-osgi-1.2.1-with-dependencies.zip解压包里的lib目录下导入Plug-ins and Fragments to import,选择以下插件:

 

以下是引用片段:
org.springframeork.bundle.spring.aop 
org.springframeork.bundle.spring.beans 
org.springframeork.bundle.spring.context 
org.springframeork.bundle.spring.core 
org.springframeork.bundle.spring.jdbc 
org.springframeork.bundle.spring.tx 
org.springframeork.osgi.aopalliance.osgi

它们也加入到Package Explorer视图中。

(4)打开Run configurations,在OSGI Framework里新建一个运行平台,并将它名字改为springDM,选择四个我们写的bundles和上面导入的spring依赖bundles,然后点击Add Required Bundles,就可以添加入有依赖其它bundles而没有引入的bundle,最后点击Validate Bundles来最后确认依赖包加全了没有,提示No problems were dected.便可以运行了。

 

(5)运行之后,我们发现控制台输出结果:
The Sum is---11
通过ss命令,如下:
5 ACTIVE osgi.example.compute.multiply_1.0.0
6 ACTIVE osgi.example.compute.add_1.0.0
7 ACTIVE osgi.example.compute.client_1.0.0
将6停掉:stop 6
然后再refresh 7,控制台输出如下结果:
The Multiply is---30
通过 ss 命令,如下:
5 ACTIVE osgi.example.compute.multiply_1.0.0
6 RESOLVED osgi.example.compute.add_1.0.0
7 ACTIVE osgi.example.compute.client_1.0.0
现在multiply处于运行状态,而add已经被停止,所以client导入的服务实际是由multiply提供的。

 

 

 

 

4. 总结

 

通过该文档,我们已经清楚了,如何使用Spring DM导出、导入服务。Spring DM的一些高级特性请查阅spring dm reference。

 

你可能感兴趣的:(spring)