在 OSGi
服务平台上构建应用时,必须考虑各种服务之间依赖关系及服务的管理,应用所依赖的服务有可能在任何时候被注销或者更新,用户在完成对 Service
的发布、查找、绑定的同时,还需要对服务的状态进行监听,以便作出适当的响应,所以在 OSGi 服务平台上,对服务依赖关系的动态管理至关重要。在
OSGi Release 4 中,提出了 Declarative Services
规范,通过该规范可以方便地对服务之间的依赖关系和状态进行监听和管理。在本文中,将对 Declarative Services
规范进行介绍并且基于该规范开发一个实例。
Declarative Services简介
Declarative Services 是一个面向服务的组件模型,它制订的目的是更方便地在 OSGi
服务平台上发布、查找、绑定服务,对服务进行动态管理,如监控服务状态以及解决服务之间的复杂的依赖关系等问题。Declarative
Services 采用服务组件的延迟加载以及组件生命周期管理的方式来控制对于内存的占用以及启动的快速,很好的解决了传统的 OSGi
服务模型在开发和部署比较复杂应用时内存占用大、启动慢等问题,并且对服务组件的描述采用XML来实现,十分便于用户理解和使用。在
Declarative Services 中,Component 可以是 Service 的提供者和引用者,一个 Component 可以提供
0 至多个 Service,也可以引用 0 至多个 Service,并且采用component 方式封装 Service,方便了对
Service 的复用,从开发者的角度来看,该服务组件模型简化了在 OSGi 服务平台中的编程模型。Declarative Services
规范参考了"Automating Service Dependency Management in a Service-Oriented
Component Model"一文的有关概念,读者可从参考资料获得该文的详细信息。
Component Satisfied 概念介绍
在 Declarative Services 中,一个服务组件是包含在 Bundle 应用中的普通的 Java
类,每个Component 可以暴露出多个服务,同时也可依赖于多个服务,通过XML文件描述和服务组件相关的信息,SCR(Service
Component
Runtime)根据服务组件配置文件控制着组件配置的激活(Activate)和钝化(Deactivate),服务组件配置文件包括如组件的类型、组
件的实现以及引用的服务等信息。在详细介绍服务组件(Component)之前,我们必须了解 Component Satisfied 的概念,在
Declarative Services 中,Component Satisfied 与 Component 的生命周期密切相关。如
Component 激活的前提条件之一就是 Component Satisfied,而在 Component 的运行过程中,出现
Unsatisfied 时,Component 将被钝化。主要由以下两点决定 Component 是否处于Satisfied 状态:
Component 为 Enabled 状态,Component 的生命周期包含在引用它的 Bundle 应用的生命周期之内,只有在
Bundle 处于 Active 状态时,Component 才有可能为 Enabled 状态,在 Bundle处于 Stop
状态时,Bundle 中所有的 Component 都处在 Disabled 状态。Component 初始的Enabled
状态可以在服务组件配置文件中设定。
Component 的配置是可以被引用和解析的,Component 中引用的 Service 也是 Satisfied 的,引用的
Service 至少有一个是处于可用状态的,或者引用的 Service 在服务组件配置文件里配置了可为 0 个可用状态的 Service。
当上述两个条件中任何一个不满足时,组件配置将变为 Unsatisfied 状态,组件配置将被钝化。在理解 Component Satisfied 的概念后,下面讲解三种类型的服务组件。
Component 介绍
在 Bundle 启动时, Declarative Services 装载相应的服务组件配置文件,配置文件在MAINFEST.MF
文件的 Service-Component 属性指定,解析配置文件,获取服务组件引用的 Service ,如果判断组件 Satisfied
状态的两个条件满足时, Declarative Services 就认为这个组件是 Satisfied 的。
Immediate Component
对于 Immediate Component,如果组件配置处于 Satisfied 状态,将会立即被激活,并且如果该配置指定了服务,那么
SCR 会注册该服务并且立即激活该服务组件。在 SCR 激活组件配置时,实现服务组件类的 activate
方法将会被调用,在SCR钝化组件配置时,deactivate方法将会被调用。Immediate Component的状态图如图1所示:
图示1:Immediate Component状态图
Delayed Component
对于 Delayed Component ,如果组件配置处于Satisfied状态,该组件并不会立即被激活,Declarative
Services 会根据组件配置文件中的 Service 的配置,注册相应的Service 的信息,直到该服务组件被请求时,
Declarative Services 才会激活该组件配置 。 Delayed Component 延迟了 Component
类的创建,当该服务组件的服务收到请求时,该 Component 类的 activate 方法才会被调用。如果一个 Component 不是
Factory Component,并且在其组件配置文件中指定了服务,组件的 immediate 属性设置为 false,那么该组件就是
Delayed Component。Delayed Component 的状态图如图 2 所示:
图示2:Delayed Component 状态图
Factory Component
通过在组件配置文件中设置 Component 的 factory 属性,将 Component 声明为 Factory
Component。该组件在激活后注册的是一个 Component Factory 服务,只有在调用 Component Factory 的
newInstance 方法后才会激活相应的各个组件,每一次调用 newInstance
方法,都会创建和激活一个新的组件配置。如果在组件配置文件中声明了服务,那么在该组件激活之前,声明的服务被注册。Factory
Component 的状态图如图3所示:
图示3:Factory Component状态图
在三种类型的服务组件中,Delayed Component 很好的解决了系统服务的动态性问题,同时也节省了内存的占用。 服务组件的生命周期受 Bundle 生命周期影响,当 Bundle 停止时,那么Bundle 中所有的服务组件也就停止。
Service 的发布、查找、绑定
在 OSGi 服务平台中,大部分 Bundle 应用都是基于服务的,服务的发布、引用十分重要,下面讲一下利用服务组件如何进行 Service 的发布、查找和绑定。
Service 的发布
对于 Component 中 Service 的发布,需要在组件配置文件中定义 service 元素,该
service元素至少包括一个或多个 provide 元素,该 provide 元素定义了该 component
提供的服务接口,它只有一个属性 interface,该 interface
定义了提供服务的接口,并且允许是实现该服务接口的类名。可以看出,利用 Declarative Services 发布 Service
非常简单,只要 Component 实现了定义的 Service 的接口即可。如在本文所讲解例子中,在组件配置文件中,声明姓名查询服务如图 4
所示:
图示4:姓名查询服务声明
Service 的查找和绑定
在 Declarative Services 中,Component 所引用的服务,称为 Target Service,当
Component 中引用的 Target Service 也是 Satisfied 时,即引用的 Service
至少有一个是处于可用状态的,或者引用的 Service 在服务组件配置文件里配置了可为 0 个可用状态的
Service,组件配置才有可能被激活。在组件实现类中,有两种策略可以获得在组件配置文件里指定的 Target Service,是事件策略和
Lookup 策略。
事件策略
在服务组件激活的过程中,SCR 必须将组件配置文件里指定的 Target Service 绑定到组件配置中。在事件策略中,SCR
通过调用组件实现类的一个方法将 Target Service 绑定到组件中,同样,SCR
通过调用另外一个方法来取消绑定,这些方法在组件配置文件中 reference 元素的bind 和 unbind
属性指定。事件策略主要适用于服务组件所引用的 Target Service
处在动态变化中。如在本文例子中,如果采用事件策略引用姓名查询服务,在配置文件中声明和 Component 实现类中引用服务分别如图示 5、图示
6 所示:
图示5:采用事件策略的组件配置文件
图示 6:采用事件策略的绑定姓名查询服务
Lookup 策略
在组件实现类中,通过调用 ComponentContext 的 locateService 方法来定位所引用的 Target
Service ,该方法的参数是在组件配置文件里指定的 reference 元素的 name 属性。如在本文例子中,如果采用 Lookup
策略引用姓名查询服务,在配置文件中声明和 Component实现类中引用服务分别如图示 7、图示 8 所示:
图示7:采用 Lookup 策略的组件配置文件
图示 8:采用 Lookup 策略的引用姓名查询服务
在 OSGi 服务平台中,即便 Component 已经绑定所引用的 Target
Service,但是由于服务的动态性,它可能在任何时刻被注册、替换或者注销,这些变化可能使服务组件所引用的 Target Service
变成过时的引用,所以,在 Declarative Services
中,当这些情况发生时,Component必须采取某种策略去处理这些变化。Declarative Services 提供两种策略,一种是
static 策略 ,另外一种是 dynamic 策略 ,默认情况下 Component 采用的是 static 策略。当采用static
策略时,如果引用的 Target Service 发生了变化,那么组件配置会被重新装载并激活。当采用 dynamic 策略时,SCR
在不钝化组件配置的情况下可以改变绑定的 Target Service。此外,Declarative Services
还提供很多功能,如可通过在 Component 的 reference 元素中增加 target 属性来实现对所引用 Service
进行过滤;如可增加 cardinality 属性来对引用 Service 的数量进行控制。关于 Declarative Services
更详细的信息,请读者参见本文的参考资料。
使用 Eclipse 开发服务组件
在本文中,我们结合 Equinox 项目关于 Declarative Services 的实现,开发两个使用服务组件的 Bundle
应用,其中第一个 Bundle 的服务组件的配置文件中声明注册了一个姓名查询服务,用于判断所给姓名是否在已定义的查询列表中;第二个
Bundle 应用的服务组件的配置文件中静态引用了第一个 Bundle
应用服务组件所注册的姓名查询服务,如果用户所给的姓名包含在查询列表中,将返回正确的信息。最后,将开发的 Bundle 应用部署的
Equinox OSGi 框架中,用户可以在 OSGi 控制命令行中输入命令来查询关于框架和 Bundle
应用的具体信息。读者可以从参考资料中获得本文 Bundle 应用的源代码。关于 Equinox 项目的详细信息,请查阅参考资料信息。
(1)首先定义所提供服务的接口,然后 Bundle 应用的服务组件实现这个服务接口。在本例中,定义姓名查询接口 NameService.java。下面是该接口的源代码:
NameService Interface 源代码
package ds.example.service;
/**
* A simple service interface that defines a name service.
* A name service simply verifies the existence of a Name.
**/
public interface NameService {
/**
* Check for the existence of a Name.
* @param name the Name to be checked.
* @return true if the Name is in the list,
* false otherwise.
**/
public boolean checkName(String name);
}
|
该服务接口很简单,只包含一个需要实现的方法。通常为了将服务接口和服务实现相分离,要将该服务接口单独放在一个包内。
(2 ) 定义 Bundle 描述文件 MANIFEST.MF,Bundle 应用 dsExample 的 MANIFEST.MF 文件如下:
MANIFEST.MF 文件信息
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: DsExample Service
Bundle-SymbolicName: dsExample
Bundle-Version: 1.0.0
Bundle-Localization: plugin
Import-Package: org.osgi.framework;version="1.3.0",
org.osgi.service.component;version="1.0.0"
Service-Component: OSGI-INF/component.xml
Export-Package: ds.example.service
|
其中,Service-Component 属性指定了该 Bundle
应用的服务组件配置文件,在该配置文件中声明服务并且指定了实现该服务的组件;Export-Package 属性指定了该 Bundle
输出的共享包,该属性可以使其他的 Bundle 应用引用所定义的服务接口。
(3)编辑该 Bundle 应用的服务组件配置文件,正如前面所讲 Service 的发布那样,该服务组件的配置文件如下:
dsExample Bundle 的组件配置文件
<?xml version="1.0" encoding="UTF-8"?>
<component name="dsExample">
<implementation
class="example.osgi.NameImpl"/>
<service>
<provide interface="ds.example.service.NameService"/>
</service>
</component>
|
其中,Service 元素定义了所提供服务的接口;Implementation 元素定义了实现该服务接口的组件类名。
(4)实现在服务组件配置文件中指定的服务组件,源代码如下所示:
dsExample 实现组件源代码
public class
NameImpl
implements NameService {
// The set of names contained in the arrays.
String[] m_name =
{ "Marry", "John", "David", "Rachel", "Ross" };
protected void activate(ComponentContext context) {
System.out.println("NameService Component Active,within the bundle
lifecircle.");
}
public void
deactivate(ComponentContext context)
throws Exception {
System.out.println("NameService Component Deactive,within the bundle
lifecircle.");
}
public boolean checkName(String name) {
// This is very inefficient
for
(
int i = 0; i < m_name.length; i++)
{
if (m_name[i].equals(name))
{
return true;
}
}
return false;
}
}
|
该服务组件实现了 NameService 接口,并且在服务组件激活和钝化时分别打印出相应信息,以便在运行 Bundle 应用时,能够跟踪 Component 的生命周期。
(5)创建项目名为 dsExampleClient 的 Bundle 应用,该应用的服务组件在 OSGi 平台上查询并引用
dsExample Bundle 应用已经注册的姓名查询服务,然后从标准输入读入用户所输入的姓名信息,判断所输入姓名是否有效。关于
dsExampleClient 应用,读者可从参考资料中获得完整源代码,下面只给出该 Bundle 应用的服务组件配置文件:
dsExampleClient Bundle 的组件配置文件
<?xml version="1.0" encoding="UTF-8"?>
<component name="dsExampleClient">
<implementation
class="exampleclient.osgi.CheckNameClient"/>
<reference name="nameservice"
interface="ds.example.service.NameService"
cardinality="1..1"
policy="static"
/>
</component>
|
其中,reference 元素定义了该组件所引用的服务接口,并且指明该组件采用 static 策略;Implementation 元素指定了实现组件的类。
Bundle的部署及运行
在 Eclipse 平台中,在菜单中选择 Run-->Run AS-->Equinox FrameWork 来启动 OSGi
服务平台。注意在Equinox启动配置控制台中的Target Platform
中选择org.eclipse.equinox.ds选项,该plug-in是Equinox关于Declarative
Services的实现,将两个Bundle应用设置为取消自动启动选项。当OSGi Equinox
FrameWork启动后,在OSGi控制命令台中输入ss命令,可以查看OSGi服务平台中已经安装的Bundle应用信息及其状态。如图9所示,可以
看到dsExample和dsExampleClient Bundle应用处于Resolved状态。
图示9:Bundle状态查询
在OSGi控制命令台中利用start命令启动 dsExample 应用,用ss命令查看启动后的Bundle应用信息及其状态,可以看出
dsExample Bundle
处于Active状态,但是该Bundle的服务组件并没有被激活,如果被激活,将会在OSGi控制命令台中打印出"NameService
Component Active,within the bundle lifecircle."字样,说明该服务组件为 Delayed
Component 类型,该组件并不会立即被激活,直到该服务组件被请求时, Declarative Services
才会激活该组件配置,Delayed Component延迟了组件的加载,节省了内存的占用 ,如图10所示:
图示10:启动dsExample Bundle
在OSGi控制命令台中利用start命令启动dsExampleClient应用,可以看出两个 Bundle的服务组件相继被激活,如图11所示:
图示11:启动dsExampleClient Bundle
在OSGi控制命令台中利用stop命令停止dsExample应用,可以看出两个 Bundle的服务组件相继被钝化,如图12所示:
图示12:停止dsExample Bundle
需要注意的是服务组件的生命周期受 Bundle 生命周期的影响,当 Bundle 停止时,那么Bundle 中所有的服务组件也就停止。
总结
Declarative Services 是一个面向服务的组件模型,其目的是更方便地在 OSGi
服务平台上发布、查找、绑定服务,对服务进行动态管理。Declarative Services
采用服务组件的延迟加载以及组件生命周期管理的方式来控制对于内存的占用以及启动的快速,对 Service
的动态管理,使得系统可以根据系统运行的情况做出及时的响应,增强了系统的稳定性和灵活性。项目Gravity
也采用了类似的机制,有兴趣的读者可以参见参考资料中的详细信息。