OSGi R4服务平台核心规范 :第五章 服务层(1)

 由  满江红开放技术研究组织 发布

版权说明

本文档版权归原作译者所有。 在免费、且无任何附加条件的前提下,可在网络媒体中自由传播。

如需部分或者全文引用,请事先征求作译者意见。

如果本文对您有些许帮助,表达谢意的最好方式,是将您发现的问题和文档改进意见及时反馈给作者。当然,倘若有时间和能力,能为技术群体无偿贡献自己的所学为最好的回馈。

本文档可从http://www.redsaga.com获取最新更新信息

5.1 简介

OSGi服务层定义了一个和生命周期层紧密结合的动态协作模型。服务模型包括发布、查找和绑定模型。一个服务(service)就是一个通过服务登记来注册到一个或者多个Java接口下的Java对象。bundle可以注册服务,查找服务,接收注册服务的状态改变信息。

5.1.1. 要点

  • 协作性 — 服务层必须为bundle提供一种机制来发布、查找和绑定bundle之间的服务,而无需事先知道bundle的信息。

  • 动态性 — 服务层机制必须能够处理外界或者是子结构的变化。

  • 安全 — 必须能够限定对服务的访问。

  • 深入性 — 对服务层的内部状态可以进行完全控制。

  • 版本控制 — 提供对bundle以及对它们服务更新的控制。

  • 持久性 — 在框架重启过程中提供方法来跟踪服务。

5.1.2. 名词

  • 服务(Service) – 在一个或多个接口下注册了服务登记的具有属性值的类。可以由bundle查找发现并使用。

  • 服务注册中心(Service Registry) – 保持服务注册。

  • 服务引用(Service Reference) – 对一个服务的引用。提供对一个服务属性的访问但不是对实际的服务对象。对服务对象的访问必须要通过bundle的BundleContext来进行。

  • 服务注册(Service Registration) – 当一个服务注册之后提供凭据。服务注册允许对服务进行更新和取消注册。

  • 服务权限(Service Permission) – 当注册或者使用一个服务的时候,使用一个接口名称来进行权限控制。

  • 服务工厂(Service Factory) – 由提供服务的bundle来为每一个使用服务的bundle定制一个服务对象的便捷方法。

  • 服务监听器(Service Listener) – 服务事件监听器。

  • 服务事件(Service Event) – 一个服务对象的注册、更改、取消注册的事件。

  • 过滤器(Filter) – 实现了一个简单但是功能强大的过滤器语言的对象。可以用于属性选择。

  • 语法错误异常(Invalid Syntax Exception) – 当一个过滤器表达式包含错误的时候抛出的异常。

http://assets.osgi.com.cn/article/7289400/1.png

5.2. 服务

在OSGi服务平台下,bundle建立在一系列的相互协作的可用服务之上,这些服务共享一个服务注册中心。这样一个OSGi服务在语义上通过它的服务接口来定义,并实现为一个服务对象。

在服务接口中应该尽可能少的指定实现细节。OSGi规定了很多常用的服务接口,以后还会增加。

服务对象是属于bundle的,而且在bundle之内运行。bundle必须要将服务对象注册到框架的服务注册中心,这样,才可以在框架的控制下来为其他bundle提供服务。

提供服务的bundle和使用服务的bundle之间的依赖关系由框架来进行管理。例如,当停止一个bundle后,这个bundle在框架中注册的服务必须要自动的取消注册。

框架将服务映射到框架下的服务对象,而且,框架提供了一种简单而强大的查询机制,通过使用这种机制,bundle就可以请求它需要的服务。框架也提供了一种事件机制,这样,bundle就可以接收到服务注册、更改和取消注册的消息。

5.2.1. 服务引用

通常,注册的服务是通过一个ServiceReference对象来引用的。这样,在bundle只需要知道一个服务而不是一个服务对象的情况下,就可以避免创建bundle之间的不必要的动态服务依赖关系

可以把一个ServiceReference对象保存并发送给其他bundle,而不会带来依赖关系。当bundle需要使用服务时,就可以通过将ServiceReference对象作为方法getService的参数来获取服务,即为BundleContext. getService(ServiceReference)。详情参阅服务定位一节。

在ServiceReference对象中封装了属性和服务对象的其他元数据信息。其他bundle可以通过查找元数据信息来选择最合适的服务。 当bundle在框架的服务注册中心查找服务时,框架必须将请求服务目标的ServiceReference对象发送给请求者bundle,而不是直接发送服务对象本身。也可以通过ServiceRegistration对象来得到一个ServiceReference对象。 只有当服务对象注册之后,ServiceReference对象才是有效的。而只要ServiceReference对象存在,那么它的属性应该是可以使用的。

5.2.2. 服务接口

在服务接口中定义了服务的公有方法。实际上,bundle的开发人员通过实现服务接口来创建一个服务对象,并将服务对象注册到框架的服务注册中心。一旦bundle通过一个接口名称注册了一个服务对象,那么bundle就可以通过这个接口名称来获得相关服务,并可以通过接口中定义的方法来使用服务。同时,框架也支持使用类名称来注册服务,因此,在本规范中,服务接口同时包括了接口和类。

当从框架来请求一个服务时,bundle可以指定提供服务的对象必须要实现的服务接口名称。在请求中,bundle也可以通过一个过滤器字符串来限定搜索范围。

5.2.3. 注册服务

bundle通过在框架的服务注册中心注册一个服务对象来发布一个服务。那么安装在OSGi环境下的其它bundle就可以访问到在框架中注册的服务对象。

每一个注册服务对象都有一个惟一的ServiceRegistration对象,而且有一个或者多个引用的ServiceReference对象。这些ServiceReference对象公开了服务注册的属性,包括服务实现的一系列接口信息。可以使用ServiceReference对象来获取实现了特定接口的服务对象。

在框架中,允许bundle动态的注册和取消注册服务对象。因此,bundle可以在STARTING、ACTIVE 或者 STOPPING状态下注册服务对象。

bundle通过使用BundleContext.registerService方法,在框架中注册一个服务对象:

  • registerService(String,Object,Dictionary) – 只实现了一个服务接口的服务对象注册。

  • registerService(String[],Object,Dictionary) – 实现了多个服务接口的服务对象注册。

bundle需要注册的服务实现的服务接口名称以参数形式提供给registerService方法。框架必须要确保服务对象是每一个指定的服务接口的一个实例,或者这个对象是一个服务工厂。参阅服务工厂一节。

为了进行上述检查,框架必须要从bundle或者一个共享包中为每一个指定的服务接口加载Class对象。对于每一个Class对象,在以服务对象为参数的Class对象中,必须调用它的Class.isInstance方法,并且返回true。

以后,需要注册的服务对象也许会使用一个Dictionary对象来描述,在这个Dictionary 对象中,包含了服务的属性的键值对序列。

如果一个服务对象成功注册,则将它实现的服务接口名称自动添加到服务对象的objectClass属性下。这个值必须由框架来自动设置,而由bundle提供的值则被覆盖了。

服务对象成功注册之后,框架给调用者返回一个ServiceRegistration对象。对服务的取消注册只能由ServiceRegistration对象(参阅unregister方法)的持有者进行。即使一个服务对象进行了多次注册,每次成功注册的服务对象必须产生一个惟一的ServiceRegistration对象。

使用ServiceRegistration对象是在服务对象注册后对它的属性进行可靠更改的惟一途径(参考setProperties方法)。在服务对象注册之后对它的Dictionary对象进行更改可能不会对服务属性有任何影响。

服务对象的注册处理过程包含了权限检查。注册服务的bundle必须要有ServicePermission权限:ServicePermission [,REGISTER],用来注册一个指定了所有服务接口的服务对象。否则,不能注册服务对象,并抛出一个安全异常(SecurityException)。

5.2.4. 服务注册对象的过早请求

服务注册的消息会通知所有注册了ServiceListener的对象。这是一个同步发送的过程。也就是说监听器可以在registerService方法返回之前就访问服务并调用它的方法。特定情况下,在这种回调中需要访问到ServiceRegistration对象。但是,注册服务的bundle还没有接收到ServiceRegistration对象。下图展示了这样一个序列:

http://assets.osgi.com.cn/article/7289400/2.png

在前面的例子中,对ServiceRegistration对象的访问可以通过ServiceFactory对象来获取。如果已经注册了一个ServiceFactory对象,框架在注册服务的bundle中回调ServiceFactory的getService方法。那么在这个方法执行完之后,通过这个方法的参数就可以获得ServiceRegistration对象。

5.2.5. 服务属性

属性信息保存在键值对中。键值为一个字符串对象,值则为Filter对象可以识别的类型。如果一个属性有多个值,则可以使用数组和Vector对象。

属性值应该限定为标准或原始的Java类型,以防止对bundle内部的依赖。框架不能检测到bundle之间通过服务属性产生的依赖关系。

属性键名称是不区分大小写的。ObjectClass、OBJECTCLASS和objectclass都是指同样的含义。如果调用方法ServiceReference.getPropertyKeys,那么框架必须返回它上次设置的属性值。如果Dictionary对象中存在只是大小写不同的键,那么框架必须要抛出一个异常。

服务属性提供了服务对象的信息,不应该将这些属性值用于服务的实际功能中。对注册服务的属性值更改时一种潜在的代价颇高的操作。例如,框架会提前将属性值加入到索引中以加快以后的查找速度。

Filter接口支持复杂的过滤规则;可以用于查找匹配的服务对象。因此,框架的服务注册中心中所有属性共享一个名称空间。导致的结果就是为了防止产生名称冲突,需要使用描述性名称或者是形式化定义的更短的名称来区别不同的属性,所有的属性名称前缀为service,同时在OSGi规范中保留了objectClass属性。

下表描述了预定义的标准服务属性:

属性 类型 常量 描述
objectClass String[] OBJECTCLASS objectClass属性包含了注册到框架中的服务对象所有实现的接口的集合。这个属性必须由框架自动设置。当使用BundleContext的getService方法获取服务对象时,框架必须要保证这时候服务对象可以声明为属性中指定的任何接口名称。
service.description String SERVICE_DESCRIPTION service.description属性用于文档性的描述,这个属性是可选的。在框架和bundle中可以利用这个属性来对其提供的服务注册对象作一个简短的描述。这个属性的主要目的在于进行调试,这是由于没有对这个属性的本地化支持。
service.id Long SERVICE_ID 每一个注册了的服务对象都由框架分配了一个惟一的service.id。将这个标志数字添加到服务对象的属性中。框架给每一个注册的服务对象分配一个惟一的标志值,这个值要比原来分配的任何值要大,也就是说是递增分配的。
service.pid String SERVICE_PID service.pid属性是可选的,标记了服务对象的持久惟一标记。
service.ranking Integer SERVICE_RANKING 当注册一个服务对象时,bundle也可以可选指定一个service.ranking数字作为服务对象的属性值。如果存在多个可用的服务接口,那么如果一个服务对象具有最大的SERVICE_RANKING值或者是它的值等于最小的SERVICE_ID值,那么就由框架返回这个服务对象。
service.vendor String SERVICE_VENDOR 这是一个可选属性,描述服务对象的开发商信息。

5.2.6. 持久标志符(PID)

持久标志符(PID)用于在框架重启之后标志服务。对于每次注册引用同一个实体的服务来说,应该使用包含了PID的服务属性。PID的服务属性定义为service.pid。对于不同愿景的框架中都持久性存在的服务,PID是这种服务的惟一标记。对于一个给定的服务,应该使用相同的PID。如果停止了一个bundle之后又重新启动它,那么必须使用相同的PID。

PID的格式如下所示:

pid ::= symbolic-name // 参考 1.4.2

PID对于每一个服务来说是惟一的。bundle不能使用同样的PID来注册多个服务,而且也不能和其他bundle使用相同的PID,如果使用了相同的PID,那么这是一种错误的用法。

5.2.7. 服务定位

为了使用一个服务对象并调用它的方法,bundle必须首先获得服务的ServiceReference对象,在接口BundleContext中定义了两个方法来从框架中获取一个ServiceReference对象:

  • getServiceReference(String) – 这个方法返回一个服务对象实现的ServiceReference对象,这个服务对象实现并注册了指定字符串参数的接口。如果存在多个这样的服务对象,那么选择一个具有最大SERVICERANKING值的服务对象。如果在队列中存在多个符合这样条件的服务对象,那么就使用具有最小SERVICEID值的服务对象(也就是最先注册的)。

  • getServiceReferences(String,String) – 这个方法返回一个ServiceReference对象数组,返回数据符合以下条件:

  • 实现并注册了指定的服务接口

  • 符合指定的查找过滤器条件。在Filter一节中解释了过滤器语法。

如果没有找到符合条件的服务对象,那么上述两个方法都返回null值。否则,调用者接收到一个或多个ServiceReference对象。可以通过ServiceReference对象来取得服务对象的属性值,还可以通过BundleContext对象使用ServiceReference对象获得实际的服务对象。

这两种方法的调用都需要调用者具有ServicePermission权限:ServicePermission[, GET]来通过指定服务接口获得服务对象。如果调用者没有权限,那么这两个方法都是返回null值。

5.2.8. 获取服务属性

为了允许访问服务对象,ServiceReference接口定义了以下两种方法: - getPropertyKeys() – 返回所与可用的属性键数组。 - getProperty(String) – 返回指定键名称的键值。 即使从框架中取消注册了服务对象,也可以通过上述方法来获得信息。当ServiceReference对象和登录服务关联时,这种机制就非常有用。

5.2.9. 获取服务对象

通过使用BundleContext对象来获取一个实际的服务对象,这样框架就可以来管理这些依赖关系了。如果一个bundle需要一个服务对象,这样这个bundle就依赖于注册的服务对象的生命周期。对于这种依赖可以通过可以获取服务对象的BundleContext对象来跟踪,这也就是为什么在bundle之间共享BundleContext对象需要非常的小心。 BundleContext对象的方法BundleContext.getService(ServiceReference)返回一个实现了接口的对象,在objectClass属性中定义了需要这些接口。getService方法有以下特征: - 如果服务对象取消注册之后,那么方法返回null值。 - 检查调用者是否有权限:ServicePermission[,GET],至少需要有一个注册的服务接口的权限。权限检查是非常有必要的,这样就可以传递ServiceReference对象而无须考虑泄密这样的安全问题。 - BundleContext的对服务对象的使用计数加一。 - 如果服务对象没有实现ServiceFactory接口,那么直接返回。否则如果BundleContext的服务对象是一个,如果这个对象声明为一个ServiceFactory 对象,则调用getService方法来为调用的bundle创建一个定制的服务对象。再则,返回一个定制对象的缓冲拷贝。关于ServiceFactory对象参阅服务工厂一节。

5.2.10. 服务的其它信息

在Bundle接口中定义了以下两种方法来返回与服务使用的bundle有关的信息:

  • getRegisteredServices() – 返回bundle在框架中注册的服务对象。

  • getServicesInUse() – 返回bundle使用的服务对象。

5.3. 服务事件

  • ServiceEvent – 报告服务对象的注册、取消注册和属性改变事件。所有这些事件都是同步发送的。可以通过getType方法来获取事件的类型,这个方法返回的类型是int类型的,关于事件类型的定义以后可能会扩充,而不可识别的事件类型则是忽略处理的。
  • ServiceListener – 当对一个服务对象进行注册或者改变或者是在取消注册的过程中时,使用一个ServiceEvent事件来调用。当一个ServiceEvent发生之后,对于每一个注册的监听器必须要对其进行安全检查。只有注册监听器的bundle具有对至少一个注册的服务接口的以下权限时:ServicePermission[,GET],才可以调用监听器。

使用服务对象的bundle应该注册一个ServiceListener对象来跟踪服务对象的可用性,并在取消注册服务对象时采取合适的动作。

5.4. 过期引用

框架必须要管理bundle之间的依赖关系。这种管理受限于框架的结构。bundle通过监听框架产生的事件来清理和移除过期引用。

过期引用是指对这样一个Java对象的引用,这个对象所在的类加载器所属的bundle已经停止,或者所关联的服务对象已经是取消注册了。标准Java处理中并没有提供任何方法来清除这些过期引用,这就需要bundle的开发人员必须对他们的代码进行分析,并确保删除了过期引用。

过期引用存在潜在的危害,这是由于它们阻止了Java的垃圾收集器对已经停止的bundle的类以及可能的实例进行回收。这样就会导致显著的内存消耗,并可能使得对本地代码的更新失败。因此,强烈建议使用服务的bundle使用服务跟踪器(Service Tracker)或者是公布服务(Declarative Services)。

服务开发人员可以通过使用如下机制来减小(并不能完全消除)过期引用的危害:

  • 通过使用ServiceFactory接口来实现服务对象。ServiceFactory接口中的方法可以简单跟踪使用服务对象的bundle。参阅服务工厂一节。
  • 间接使用服务对象。服务对象提供给其他bundle的应该是一个对实际服务对象的一个指针。当服务对象不可用之后,将指针设置为null值,这样就有效的移除了对实际服务对象的引用。 一个已经取消注册的服务的行为是不确定的。这些服务也许可以正常工作,也许会抛出一个异常,这取决于它们的实现情况。应该将这种类型的错误记录到日志中。
查看评论