第9章 服务注册表
OSGi服务注册表能让一个bundle通过一组Java接口向共享注册表发布对象。发布到注册表中的服务同时具有服务属性。服务注册表是OSGi的一个关键特性,它基于面向服务的范式(发布/查找/绑定),将不同的bunde解耦,提升了动态协作模型。
Gemini Blueprint紧密地集成了服务注册表,允许客户端以POJO友好的方式发布、查找、绑定服务,而无需将它们和OSGi的API耦合到一起。
通过使用Spring的osgi命名空间(参见附录F,Gemini Blueprint Schema),可以定义那些Spring bean会被导出为OSGi服务,以及如何导出,可以定义OSGi服务注册表中的服务作为bean导入时的条件和方式。
和其它的命名空间一样,osgi命名空间可以被嵌入或者嵌套到顶级命名空间(例如Spring的beans命名空间),或者设置为默认的命名空间。
下面的例子演示如何在Spring的beans元素中使用osgi命名空间:
<?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.eclipse.org/gemini/blueprint/schema/blueprint" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.eclipse.org/gemini/blueprint/schema/blueprint http://www.eclipse.org/gemini/blueprint/schema/blueprint/gemini-blueprint.xsd"> <osgi:service id="simpleServiceOsgi" ref="simpleService" interface="org.xyz.MyService" /> </beans> | ||
| 使用Spring框架的beans模式作为默认的命名空间 | |
| 导入Gemini Blueprint模式,并将其命名空间与一个前缀关联(上例中为osgi) | |
| 确保导入的Spring beans模式的版本为3.0或者以上版本 | |
| 使用声明的命名空间前缀(osgi)定义Gemini Blueprint元素 |
Spring DM用户仍然可以使用它的命名空间,Gemini Blueprint bundle是支持的。这样上面的配置就变成:
<?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"> <osgi:service id="simpleServiceOsgi" ref="simpleService" interface="org.xyz.MyService" /> </beans> | ||
| 使用Spring框架的beans模式作为默认的命名空间 | |
| 导入Gemini Blueprint模式,并将其命名空间与一个前缀关联(上例中就是osgi) | |
| 确保导入的Spring的beans模式的版本号为3.0 | |
| 使用声明的命名空间前缀(osgi)定义Gemini Blueprint元素 |
本文档通篇会交替使用Gemini Blueprint命名空间和Spring DM命名空间-实际上,仔细观察会发现,其实这两个命名空间是完全相同的(除了schema位置声明不一样)。新的应用应该尽可能的使用Gemini Blueprint命名空间,虽然Spring DM命名空间也支持,但是它们已经被废弃了。
用OSGi的命名空间作为顶级命名空间,同样的服务声明如下:
<?xml version="1.0" encoding="UTF-8"?> <beans:beans xmlns="http://www.eclipse.org/gemini/blueprint/schema/blueprint" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:beans="http://www.springframework.org/schema/beans" xsi:schemaLocation="http://www.eclipse.org/gemini/blueprint/schema/blueprint http://www.eclipse.org/gemini/blueprint/schema/blueprint/gemini-blueprint.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <service id="simpleServiceOsgi" ref="simpleService" interface="org.xyz.MyService" /> </beans:beans> |
| beans根元素必须用Spring框架的beans模式前缀(beans)修饰 |
| Gemini Blueprint模式用作默认的命名空间 |
| 导入Spring框架beans模式,并将其命名空间与一个前缀关联(上例中为beans) |
| 确保导入的Spring beans模式版本为2.5 |
| 使用Gemini Blueprint元素,无需任何前缀 |
当使用前一节介绍的推荐配置,用一个专门的文件配置与OSGi有关的声明时,使用OSGi命名空间作为顶级命名空间是极其方便的。
9.1. 将Spring Bean作为OSGi服务导出
service元素用于定义一个表示OSGi服务的bean。对于类和对象来说,没有任何要求,实际上任何bean都可以导出。最低限度就是你要指定导出的bean和要发布的服务接口。
| 注意 |
发布的接口由服务消费者用于确定这个服务。服务实例必须实现这个接口-指定一个没有实现的接口将会导致错误。术语interface 用作抽象形式,实际上可以指定服务实例实现的任何接口或者继承的任何类。 |
例如,下面的声明
<service ref="beanToPublish" interface="com.xyz.MessageService"/>
将名字为beanToPublish的bean以com.xyz.MessageService接口导出。发布的服务有一个服务属性,名字为org.eclipse.gemini.blueprint.bean.name,它的值被设置为注册的目标bean的名字(这个案例中就是beanToPublish)。
导出命名bean的另一种选择是,被导出到服务注册表中的bean定义为service元素内的匿名内部bean。这时,顶级命名空间同城就是beans命名空间:
<osgi:service interface="com.xyz.MessageService"> <bean class="SomeClass"> ... </bean> </osgi:service> |
9.1.1. 服务注册
每一个服务声明都提供了对导出服务的控制访问。 这个声明返回类型为org.osgi.framework.ServiceRegistration的元素,用于读取或者修改OSGi服务发布的属性。Since DM 2.X中这个定义已经与Blueprint规范对齐了,注销服务不再可能 (如果调用了注销方法,就会抛出一个异常)。另外,返回的服务注册对象会跟踪注册服务(如果出现多次注册,那么这些注册都会反映到返回的注册对象中)。参见 第9.2.2.2节 “服务导出与服务导入之间的关系” 了解何时注册和注销服务。
要使用服务注册,只需简单的将服务bean定义注入到相关的类中。下面的例子延时更新服务属性的方式:
<service id="myServiceRegistration" ref="beanToPublish" interface="com.xyz.MessageService"/>
<bean id="propUpdater" class="com.xyz.ServicePropertiesUpdater"> <property name="serviceRegistration ref="myServiceRegistration"/> </bean> |
这里ServicePropertiesUpdater具有如下的定义:
public class ServicePropertiesUpdater implements BeanNameAware {
private ServiceRegistration serviceRegistration; private String beanName;
public void setServiceRegistration(ServiceRegistration serviceRegistration) { this.serviceRegistration = serviceRegistration; }
public void setBeanName(String beanName) { this.beanName = beanName; }
public void update() { ServiceReference reference = serviceRegistration.getReference(); // get current properties Dictionary dictionary = OsgiServiceReferenceUtils.getServiceProperties(reference); dictionary.put("last-update", new Date()); dictionary.put("updated-by", beanName); dictionary.put("user.name", System.getProperties().getProperty("java.version")); // update properties serviceRegistration.setProperties(dictionary); } } |
每一次调用update方法时都会获取服务属性,并添加新的属性,最后更新服务注册对象。
9.1.2. 服务工厂org.osgi.framework.ServiceFactory
OSGi服务平台核心规范不仅允许直接注册服务,也支持按需创建服务,即通过 org.osgi.framework.ServiceFactory 接口(参见5.6节)。Gemini Blueprint/Spring DM能识别这个OSGi接口,并承认它的合同,对于每一个bundle请求都能转发给实现了这个服务的后端bean。
作为选择,要实现OSGi的API,可以使用Gemini Blueprint/Spring DM引入的bundle的scope,它提供了“一个bundle一个实例”的合同(参见第7.5 “Bundle Scope” )。要声明bean的scope 为bundle,简单的定义bean元素的scope属性:
<osgi:service ref="beanToBeExported" interface="com.xyz.MessageService"/> <bean id="beanToBeExported" scope="bundle" class="com.xyz.MessageServiceImpl"/> |
9.1.3. 控制导出服务发布的接口
OSGi服务平台核心规范定义了术语“服务接口”来表示服务的公共方法。通常服务接口都是Java接口,但是这个规范也支持用类名注册服务对象, 这样术语“服务接口”可以被解释为接口或类的引用。
有几个选项用于指定以那些服务接口导出服务。正上所述,最简单的机制就是使用interface属性,指定接口的全限定名。要将服务以多个接口注册,可以在servcie元素中使用内嵌的interfaces元素。
<osgi:service ref="beanToBeExported"> <osgi:interfaces> <value>com.xyz.MessageService</value> <value>com.xyz.MarkerInterface</value> </osgi:interfaces> </osgi:service> |
同时使用interface属性和interfaces元素是不合法的,只能使用其中一个。
9.1.3.1. 在运行时检测发布的接口
可见层次 |
注意,当使用auto-export属性时,导出服务时,只有那些对bundle可见的类型才会被注册。 例如,如果SI不再导出bundle的类路已经中,即使使用了auto-export="interfaces",超接口SI不会作为支持的服务接口被导出。 即使服务类没有根据父类实现SI,如果声明的bundle没有导入这个接口,那么导出的服务对这个类也是可知的。虽然这看起来有点与直觉相违背,但是它实际上是OSGi的最强大的特性,它让bundle的创建者可以控制类的可见性和路径。 请参见FAQ了解更多详细的解释。 |
使用auto-export属性,你可以避免显式声明服务接口,避免分析对象类的继承层次和接口。
auto-export属性可能具有下面的四个值之一:
- disabled : 这是它的默认值。不自动检测服务接口,但必须使用interface属性或者interfaces元素。
- interfaces : 使用导出bean实现的所有Java接口类型注册服务
- class-hierarchy : 使用导出bean实现类型和超类型注册服务
- all-classes : 使用导出bean实现类型和超类型加上这个bean实现的所有接口注册服务
auto-export和interface(s)选项不是互斥的,两个选项可同时使用,以实现发布接口的细粒度控制。然而,前一个选项对于大多数场景应该足够了。
例如,为了自动以bean支持的所有接口注册bean,你可以这样声明:
<service ref="beanToBeExported" auto-export="interfaces"/>
考虑一下下面的接口层次:
public interface SuperInterface {}
public interface SubInterface extends SuperInterface {}
这样,当查找支持SuperInterface接口的服务时,以SubInterface接口注册的服务不会匹配到。因为这个原因,最佳实践是你要明确导出注册服务支持的所有接口,即使用interfaces元素或者auto-export="interfaces"。
9.1.4. 控制导出服务的发布的属性
如前所述,导出服务总是以服务属性org.eclipse.gemini.blueprint.bean.name注册,它的值设置为导出bean的名字。从DM 2.x版本开始,bean的名字也以osgi.service.blueprint.compname属性发布(OSGi 4.2 Blueprint规范引入)。其它的服务属性可以通过内嵌的service-properties元素指定。service-properties元素包含键值对,这些键值对会包含在服务发布的属性中。Key必须是字符串,Value必须是OSGi过滤器能够识别的类型。参见OSGi服务平台核心规范了解属性值如何匹配过滤器表达式。
service-properties元素必须包含至少一个Spring beans命名空间中的entry元素。例如:
<service ref="beanToBeExported" interface="com.xyz.MyServiceInterface"> <service-properties> <beans:entry key="myOtherKey" value="aStringValue"/> <beans:entry key="aThirdKey" value-ref="beanToExposeAsProperty"/> </service-properties> </service> |
非String值可以强制指定值类型。考虑OSGi事件用户(org.osgi.service.event.EventHandler)的发布,它需要指定监视的主题为event.topics属性下的数组。下面列出了如何实现这些配置:
<osgi:service id="eventMonitorService" ref="someBean" interface="org.osgi.service.event.EventHandler"> <osgi:service-properties value-type="java.lang.String[]"> <!-- 1 --> <entry key="event.topics" value="eventQueue"/> </osgi:service-properties> </osgi:service> |
1 指定service-properties元素内声明的所有值得类型为数组类型 |
<osgi:service id="eventMonitorService" ref="someBean" interface="org.osgi.service.event.EventHandler"> <osgi:service-properties> <entry key="event.topics"> <value type="java.lang.String[]">eventQueue</value> <!-- 1 --> </entry> </osgi:service-properties> </osgi:service> |
1 指出特定值的类型 |
<osgi:service id="eventMonitorService" ref="someBean" interface="org.osgi.service.event.EventHandler"> <osgi:service-properties> <entry key="event.topics"> <array value-type="java.lang.String"> <!-- 1 --> <value>eventQueue</value> </array> </entry> </osgi:service-properties> </osgi:service> |
1 使用Spring 3.x的<array>元素在运行中创建内嵌的数组 |
Gemini Blueprint技术路线图支持:将OSGi配置管理服务中的注册的属性导出到注册服务的属性中。参见附录D 技术路线图。
9.1.5. depends-on属性
Spring会管理service元素的显式依赖,以确保导出为服务的bean可以已经完全构造和配置完。如果服务具有隐式依赖其它组件(包含其它的服务元素),这些组件必须在服务导出前完全初始化,那么可选的depends-on属性就可以用来表达这种依赖:
<service ref="beanToBeExported" interface="com.xyz.MyServiceInterface depends-on="myOtherComponent"/>
9.1.6. context-class-loader属性
OSGi服务平台核心规范(在编写此文时的最新版本为4.x)没有详细说明,当触发从服务注册表获取的服务的操作时,那些类型和资源通过上下文类加载器是可见的。由于一些服务可能用的库假定上下文类加载器存在,Gemini Blueprint允许在服务执行期间显式控制上下文类加载器。这是通过使用service元素的context-class-loader属性实现的。
context-class-loader属性允许的值为unmanaged(默认)和service-provider。当指定的值为service-provider时,Spring Dynamic Modules确保上下文类加载器能看到导出服务所在bundle的所有的类路径。
当设置context-class-loader的值为service-provider时,服务对象会被代理处理类加载器。如果服务发布任何具体的类,那么必需使用CGLIB库。
9.1.7. ranking属性
当用服务注册表注册服务时,你可能想要指定服务的等级。(参见OSGi服务平台核心规范第5.2.5节)。当bundle在服务注册表中查找服务时,如果有两个或者两个以上匹配的服务时,等级最高的服务将会被返回。默认的等级为0。如果要为注册的服务明确的指定等级值,可以使用可选的ranking属性。
<service ref="beanToBeExported" interface="com.xyz.MyServiceInterface" ranking="9"/>
9.1.8. cache-target属性
默认情况下,每一次请求服务,都是从容器中获取的。这将允许当执行请求时,依赖于上下文的scope受限的bean行为正确。然而,有些场景,目标bean需要缓存,而不管scope是什么。例如,Blueprint规范就要求所有导出的服务具有这种行为。
为了适应所有的场景,Gemini Blueprint 1.0/Spring DM 2.0引入的了新的属性cache-target,正如它的名字一样,它允许缓存导出的bean。第一次服务注册的bean实例会被导出者在内部缓存起来,以方便后续复用。
<service ref="beanToBeExported" interface="com.xyz.MyServiceInterface" cache-target="true"/>
9.1.9. service元素属性
作为总结,下面的表格列出了每一个service元素属性的名字、可能的值以及简要描述。
表9.1. OSGi <service>属性
名字 | 值 | 描述 | |
interface | 全限定类名 (例如java.lang.Thread) | 导出对象的全限定类名 | |
ref | 任何bean的名字 | 引用命名bean,作为服务导出到服务注册表 | |
context-class-loader | unmanaged | 定义当调用导出服务上的操作时,如何管理上下文类加载器。默认值为unmanaged,意思是不管理上下文类加载器。如果值为 service-provider,则保证上下文类加载器能够看到导出服务所在bundle的类路径中的所有资源 | |
service-provider | |||
auto-export | disabled | 让Spring自动管理服务发布的接口。默认值为disabled。如果值为interfaces,那么发布导出服务支持所有的接口。如果值为class-hierarchy,那么发布导出服务类层次中所有的类。如果值为all-classes,那么发布所有的接口和类 | |
interfaces | |||
class-hierarchy | |||
all-classes | |||
ranking | 任何整数 | 当发布服务时,指定服务等级。默认为0 | |
cache-target | true | 指定是否需要将导出为OSGi服务的bean缓存起来(第一次注册时)。默认为false | |
false |
9.1.10. 服务注册和注销的生命周期
当应用上下文第一次创建时,由service元素定义的服务就会注册到OSGi服务注册表中。当bundle停止,应用上下文被清理时,它就会被自动注销。此外托管服务导入可用性,服务可以在运行时注销和注册。
当服务的依赖不满足注销服务时,或它处于注册状态时,如果你需要采取一些动作,那么你可以用内嵌的registration-listener元素定义监听器bean。
注册监听器的声明必须要么用ref属性引用顶级bean定义,或者声明一个内联的匿名监听器bean。例如:
<service ref="beanToBeExported" interface="SomeInterface"> <registration-listener ref="myListener" 1 registration-method="serviceRegistered 2 unregistration-method="serviceUnregistered"/> 2 <registration-listener registration-method="register"> 3 <bean class="SomeListenerClass"/> 4 </registration-listener> </service> |
|
可选的registration-method和unregistration-method属性指定了监听器bean中定义的方法的名字,在注册和注销过程中会调用这些方法。注册和注销回调方法必须匹配下面的格式的签名之一:
public void anyMethodName(ServiceType serviceInstance, Map serviceProperties);
public void anyMethodName(ServiceType serviceInstance, Dictionary serviceProperties);
这里的ServiceType可以是兼容导出的服务接口的任何类型。
注册回调的触发时机是当服务启动,初始化注册时,或者当重新注册时。注销回调的触发时机是在服务注销过程中,不管什么原因导致的注销(例如所属的bundle停止了)。
Gemini Blueprint/Spring DM会使用声明的ServiceType参数类型,当注册或者注销一个兼容的服务类型时,就会调用注册或者注销回调方法。
serviceProperties表示一个持有注册或者注销服务的所有属性。为了保持与OSGi规范的兼容,这个参数可以被强制转换为java.util.Dictionary。
9.1.10.1. OsgiServiceRegistrationListener接口
虽然我们不鼓励,但是仍然可能实现Gemini Blueprint/Spring DM特定的接口,即org.eclipse.gemini.blueprint.service.exporter.OsgiServiceRegistrationListener,这样就可以避免声明registration-method和unregistration-method。然而,因为实现了OsgiServiceRegistrationListener接口,你的代码就会依赖Gemini Blueprint/Spring DM(这与POJO的哲学是相违背的)。
监听器可以实现OsgiServiceRegistrationListener接口,声明自定义的方法。在这种情况下,会首先调用Gemini Blueprint/Spring DM接口,然后才是自定义的方法。
9.1.10.2. Blueprint服务对比
Blueprint Container提供了service元素,功能与Gemini Blueprint/Spring DM时完全相同的。在大多数情况下,它们的配置也是完全相同的。下面的表格总结了Gemini Blueprint/Spring DM和Blueprint可用的配置选项:
表9.2. Spring DM / Blueprint <service>配置比较
Gemini Blueprint/Spring DM | Blueprint |
interface | interface |
ref | ref |
auto-export | auto-export |
ranking | ranking |
context-class-loader | - |
cache-target | - (caching is always enabled) |
由于Blueprint和Gemini Blueprint/Spring DM中的registration-listener声明是完全相同的,本章节将不再谈它了。
9.2. 定义OSGi服务的引用
要使用服务,客户端必须在服务注册表中查找它们。如果找到了,OSGi平台就会返回能够获取到实际服务实例的引用。用户应该尽快返回这个服务实例,不要占用它,因为服务提供者可能会在任何时刻取消发布它。因为Java中没有机制强制清除,OSGi规范使用服务引用和前述的协议将服务提供者和提供使用者解耦。请参见OSGi核心规范第5章 服务层。
Gemini Blueprint不仅关心服务引用和实例的获取,也考虑服务的动态性,以方便OSGi服务的使用。Gemini Blueprint/Spring DM中,导入的OSGi服务变成了Spring的bean,这些bean可以像平常一样被注入到其它的应用组件中。使用服务接口类型和可选的过滤表达式匹配服务属性进行服务查找。服务实例检索在第一次请求时按需完成。一旦服务变得不可用,Gemini Blueprint/Spring DM 会自动注销服务,以避免陈旧的引用。
作为用户,你可能会找到0个、1个或多个匹配服务。在大多数场景中,只需要匹配单个服务,这些引用元素定义了对单个服务的引用。在其它情况下,尤其当使用OSGi的白板模式whiteboard pattern时, 就需要引用所有可用的匹配服务Gemini Blueprint支持用List或者Set管理一组引用。
9.2.1. 通过注解@ServiceReference引用服务
从2.1.0版本开始,gemini-blueprint允许使用@ServiceReference注解,它由专门的gemini-blueprint-extensions bundle提供。两种声明服务引用的方式具有相同的语义,即下面的bean定义
import org.eclipse.gemini.blueprint.extensions.annotation.ServiceReference; public class MyBean { @ServiceReference private Service myService; } |
等价于 |
<osgi:reference id="myService" interface="some.package.Service" /> |
然而,需要强调的是@ServiceReference是专用的扩展,不属于OSGi规范。
9.2.2. 导入服务的可用性
基数怎么了? |
Spring DM 2.x/Gemini Blueprint中,cardinality的概念被废弃了,以可用性availability来代替。主要原因是与Blueprint规范中的术语对齐,消除重复指定导入服务的数量:由于osgi元素已经指定了是否导入一个或多个服务,cardinality右边(..N/..1)是冗余的。 |
由于OSGi的动态性,服务时有时无,在特定的时间点可以可用,也可以不可用。由于用户依赖于服务的类型,这对于用户具有负面影响。Gemini Blueprint/Spring DM为了缓和这个问题,引入了可用性的概念(之前称为基数),可用性表示导入的服务是强制的,还是可选的。
正如这个名字所暗示的,强制服务暗示它是关键的应该依赖:这个服务是必需的,它的存在性会严重影响应用。
可选服务正好相反。这个服务被跟踪和绑定(如果存在的话),就像强制服务一样,但是它存在性不是必需的。从应用的角度看,这个有好处的,如果服务不可用,它的存在性不影响应用的功能。
例如,一个应用可能强制依赖于DataSource,可选依赖于日志服务:如果没有日志,它不会影响运行时,应用运行的很好;如果后端的数据库不可用,那么应用就会失败。
在Gemini Blueprint/Spring DM中,强制服务导入如果不满足,将阻止应用上下文的启动。如果上下文启动了,将会注销依赖于它的导出服务。
9.2.2.1. 强制服务和应用启动
服务的可用性影响Spring应用的启动以及任何依赖于它的导出服务的发布。正如第7.2.1 “强制服务依赖”介绍的,SpringDM应用不会启动,除非全部强制服务依赖同时可用。在初始化上下文之前,Gemini Blueprint/Spring DM会发现所有的强制服务声明,等待一段时间(默认为5分钟,参见第8.1节),等待所有导入的服务同时满足。如果超时了,而应用初始化失败(由于必需的或者强制的服务不可用)或者成功,意味着应用上下文正在被初始化。
框架阻止应用启动的这种方式只会因为必需的服务不可用而失败。这个特性避免了对bundle的启动顺序进行排序,因为这个配置已经扮演了服务栅栏的角色:无论服务按什么顺序启动,无论什么时候注册或者注销,只有当它们都存在时,上下文初始化才会开始。
| 注意 | ||
事实上,应用具有强制服务引用,但是不保证当服务引用时,合法的服务对象就一定可用,因为服务可以在任何时间注销。Gemini Blueprint/Spring DM保证在应用启动之前所有的强制服务都存在,但是它不能阻止或者保证这些服务不会在应用的生命周期内不会消失。 | |||
| 警告 | ||
声明一个强制服务引用,而这个服务是由同一个bundle导出是错误的。这种行为可能会导致应用上上文因为死锁或者超时而创建失败。 |
9.2.2.2. 服务导出者和服务导入者之间的关系
导出服务可能会直接或间接依赖于其它(导入)服务。如果其中一个服务被标记为强制(mandatory)依赖,而依赖不再满足(因为服务消失且没有合适的替代),那么依赖它的导出服务会自动从服务注册表注销,这意味着它对于客户端不再可用。如果强制依赖再次满足(注册了合适的服务),那么导出服务会被重新注册到服务注册表中。
这种自动发布管理确保只有当导出服务工作稳定时,它才会变得对潜在的OSGi客户端可用。这种行为充分利用了OSGi动态性的优势,允许应用应对运行中的变化而无需重启。
这种基于强制依赖的可用性自动注册和重新注册导出服务只会考虑声明式依赖。如果导出服务S依赖bean A,而bean A反过来又强制依赖导入服务M,这些依赖在Spring配置文件中是显式声明的(如下所示),那么当M不满足时,S就会被注销。当M再次满足时,S会重新注册。
<osgi:service id="S" ref="A" interface="SomeInterface"/> <bean id="A" class="SomeImplementation"> <property name="helperService" ref="M"/> </bean> <!-- the reference element is used to refer to a service in the service registry --> <osgi:reference id="M" interface="HelperService" availability="mandatory"/> |
然而,如果从A到M的依赖没有通过上面的配置建立起来,但是取而代之的是,在运行时通过向A传递M的引用,而不涉及任何Spring容器,那么Gemini Blueprint将不会跟踪这个依赖。
9.2.3. 引用单独的服务
reference元素用于定义引用服务注册表中的服务。
由于可能有多个服务匹配给定的描述,返回的服务是调用BundleContext.getServiceReference返回的服务。这意味着具有最高等级(ranking)的服务会被返回,或者如果对ranking有约束,具有最小服务Id的服务会被返回。(请参看OSGi规范第5章节了解服务选择算法)。
9.2.3.1. 控制Controlling The Set Of Advertised Interfaces For The Imported Service
Interface属性标识匹配服务必须事项的服务接口。例如,下面的声明创建了一个bean messageService,当为一个服务提供MessageService接口时,就会查询它,从服务注册表中返回该服务。
<reference id="messageService" interface="com.xyz.MessageService"/>
和service声明一样,当指定多个接口时,要用内嵌的interfaces元素代替interface属性:
<osgi:reference id="importedOsgiService"> <osgi:interfaces> <value>com.xyz.MessageService</value> <value>com.xyz.MarkerInterface</value> </osgi:interfaces> </osgi:reference> |
同时使用interface属性和interfaces元素是不合法的,只能用其中一个。
reference元素定义的bean实现了服务发布的所有接口(称为贪婪代理)。如果注册服务接口包含Java类类型(与接口类型相反),那么这些类型的支持就受限于Spring的AOP实现(参见Spring参考指导)。总之,如果指定的接口时类(而不是接口),那么cglib库就必须是可用的,且不支持final方法。
9.2.3.2. filter属性
可选的filter属性用于指定OSGi过滤表达式,限制服务注册表只查找匹配给定过滤条件的服务。例如:
<reference id="asyncMessageService" interface="com.xyz.MessageService"
filter="(asynchronous-delivery=true)"/>
将只匹配发布了MessageService接口的OSGi服务,属性asynchronous-delivery被设置为true。
9.2.3.3. bean-name属性
bean-name属性是指定匹配表达式最方便的快捷方式。当用service元素导出bean时,匹配表达式自动匹配bean-name的属性。(参见第9.1节“将Spring Bean作为OSGi服务导出”).
考虑下面的导出和导入声明:
<bean id="1 messageServiceBean" scope="bundle" class="com.xyz.MessageServiceImpl"/> <!-- service exporter --> <osgi:service id="messageServiceExporter" ref="1 messageServiceBean" interface="com.xyz.MessageService"/> <osgi:reference id="messageService" interface="com.xyz.MessageService" bean-name="1 messageServiceBean"/> |
将只匹配以MessageService接口发布且属性org.eclipse.gemini.blueprint.bean.name的值为messageServiceBean的服务。总之,这意味着要查处所有实现MessageService接口且命名为messageServiceBean的Gemini Blueprint/Spring DM导出bean。
9.2.3.4. availability属性
内嵌的<reference>声明 |
Gemini Blueprint/Spring DM为了检测强制依赖,任何内嵌或内部的reference声明都会被转换为顶级的reference。 |
availability属性用于指定是否匹配服务总是必需的。如果availability的值是默认的mandatory,则匹配服务必须总是存在。如果availability的值是optional,则表明匹配服务不总是必需的。(参看第9.2.3.9节 “reference和OSGi服务动态性”)。对强制服务和可选服务的行为的不同解释在第9.2.2.节导入服务的可用性。
9.2.3.5. depends-on属性
depends-on属性用于指定:不应该在服务注册表中查找服务引用,直到命名的依赖bean已经被实例化。
9.2.3.6. context-class-loader属性
OSGi服务平台核心规范(编写本文时的最新版本是4.1)不会指定,当调用从服务注册表获取的服务的操作时,哪些类型和资源对于上下文类加载器是可见的。由于一些服务可能使用一些库,对上下文类加载器做了一定的假设,Gemini Blueprint允许你显式控制上下文类加载器。这是使用reference元素的context-class-loader属性实现的。
导入者和导出者的上下文类加载器管理 |
Gemini Blueprint/Spring DM能够管理导入者和导出者两边的上下文类加载器。通常,如果Gemini Blueprint/Spring DM在两边工作,那么只能有一边的这个特性是使能的。然而,如果两边(导入者和导出者)充分利用这个能力的优势,那么调用链的最后实体会胜出。这意味着导出者设置,如果使能的话,总是会覆盖导入者的设置(无论是什么)。 |
context-class-loader属性允许的值:
- client - 在服务调用过程中,上下文类加载器保证能看到调用bundle上的类型。这时默认的选项。
- service-provider - 在服务调用过程中,上下文类加载器保证能够看到导出服务所在bundle的类路径上的类型。
- unmanaged - 在服务调用过程中,没有上下文类加载器管理
9.2.3.7. sticky属性
DM 2.x版本新引入的sticky属性指定是否一个导入者会使用后端的服务,直到它变得不可用或者是否它认为其它的候选服务更好(匹配导入者规则但是具有跟高的等级ranking,或者较低的服务id)。在Spring DM 1.x中,导入者总是选择任何时间点最好的服务。这样,如果具有较高等级的服务变得可用,代理会自动绑定它。在高动态的环境中,这种缺乏服务亲和力就变得不确定,所以DM 2.x/Gemini Blueprint修改了这种行为(与Blueprint规范对齐)。这样,服务导入者默认情况下就会变粘,意思是说代理会使用绑定的后端服务,直到它变得不可用,忽略任何其它的服务更新。只有当后端服务故障了,代理才会去查找最佳的候选服务。如果要恢复到Spring DM 1.x的行为,则标记导入者为non-sticky。
9.2.3.8. reference元素属性
作为总结,下面的表格列出了reference元素的所有属性、可能的值以及简短描述:
表9.3. OSGi <reference>属性
名字 | 值 | 描述 | |
interface | 全限定类名 (例如java.lang.Thread) | 对象导出的全限定类名 | |
filter | OSGi过滤表达式 例如(asynchronous-delivery=true) | OSGi过滤表达式用于限制服务注册表中匹配的服务 | |
bean-name | 任何字符串 | 指定过滤表达式的方便的快捷方式 | |
context-class-loader | client service-provider unmanaged | 定义调用服务上的操作时,如何管上下文类加载器。默认值为client,意思是上下文类加载器能够看到这个bundle的类路径中的资源。其它的选项,如service-provider,意思是上下文类加载器能够看到导出服务所在bundle的类路径中的资源。Unmanaged,意思是不管理任何上下文类加载器 | |
availability | optional mandatory | 定义期望的后端服务的可用性。如果不指定,默认的可用性属性为mandatory,意思是后端服务必须总是存在,如果为optional,则表明导入这可以没有后端的服务 | |
timeout | 任何长整数long | 当调用操作时,等待后端服务变得可用的时间(单位毫秒)。如果没有设置,则会应用默认的default-timeout | |
sticky | true false | 表明服务导入的粘性。如果为true(默认值),代理只在后端服务不可用时重新绑定。如果为false(Spring DM 1.x 的行为),每一次更好的候选服务出现时都会重新绑定。更好的服务定义为具有较高的ranking等级或专业较低的服务id |
9.2.3.9. reference元素和OSGi服务动态性
在应用上下文的整个生命周期内,reference元素定义的bean是不可变的(对象引用为常量)。然而,引用后端的OSGi服务可能在任何时间出现和消失。对于强者服务依赖,应用上下文的场景会阻塞,直到匹配得到服务可用。参见9.2.2, “导入服务的可用性”。
当引用后端的服务消失了,Gemini Blueprint尝试用另一个满足匹配规则的服务替换后端服务。通过注册reference-listener, 应用可以收到后端服务变化的通知。如果没有可用的匹配服务,那么就说引用不满足。不满足的强制服务导致任何依赖于它的导出服务从服务注册表中注销,直到整个引用变得再次可用。参见第9.2.2.2节 “服务导出者和导入者的关系。
当调用不满足的引用bean(optional或mandatory)上的操作时,调用就会阻塞,直到引用变得满足。Reference元素的timeout属性允许指定一个超时时间(单位毫秒)。如果在超时周期内没有匹配服务变得可用,那么就会抛出ServiceUnavailableException异常。
9.2.3.10. 得到管理服务引用
Gemini Blueprint/Spring DM可以自动将管理的OSGi服务转换为服务引用。即如果注入到bean中属性的类型为ServiceReference (而不是引用支持的服务接口),那么服务的托管OSGi ServiceReference会自动注入到服务的位置:
public class BeanWithServiceReference { private ServiceReference serviceReference; private SomeService service;
// getters/setters ommitted } | ||||
<reference id="service" interface="com.xyz.SomeService"/>
<bean id="someBean" class="BeanWithServiceReference"> <property name="serviceReference" ref="service"/> <property name="service" ref="service"/> </bean> | ||||
| ||||
| 自动将托管service转换为ServiceReference | |||
| 托管服务无需转换就可以注入 | |||
| 注意 | |||
注入的ServiceReference由Gemini Blueprint/Spring DM管理,并且随着后端的OSGi服务实例变化而变化 |
有些场景,托管ServiceReference需要获得OSGi服务。不幸地是,大多数OSGi框架都期望自己的ServiceReference类,当使用Gemini Blueprint/Spring DM托管引用时,就会失效。对于这样的场景,可以获得本地的ServiceReference绑定,将引用对象强制转换为ServiceReferenceProxy,然而调用getTargetServiceReference方法。使用上面的示例,可以使用下面的代码:
ServiceReference nativeReference = ((ServiceReferenceProxy)serviceReference).getTargetServiceReference()
返回的nativeReference可以安全地传递给OSGi框架。然而,由于不受Gemini Blueprint/Spring DM管理,它可能会引用到一个不同于导入的OSGi服务。
为了避免去同步化,考虑使用托管的ServiceReference对象,来读取绑定的OSGi服务属性,而不是获取OSGi服务(通过Gemini Blueprint/Spring DM的注入获取)。
9.2.4. 引用服务集合
自然序 与自定义顺序
Java集合API定义了两种排序接口:Comparable和Comparator。第一个接口需要对象实现自然序。String、Long和Date是实现Comparable接口的例子。然而,有一些场景的排序不是自然序,排序的对象没有实现Comparable接口。为了处理这些场景,设计了Comparator接口。
要了解更多信息,请查阅Java集合教程的对象排序章节。
有时候应用需要的不是满足条件的某个服务,而是多个服务。Gemini Blueprint/Spring DM允许匹配服务保存到List或者Set(可是排序的)。
使用List和Set管理集合的区别是相等性。注册表中两个以上的服务可能是匹配“相等”的(只是服务Id不同),它依赖于服务实现的equals方法的实现。在Set中这些服务只会存在一个,而在List会存在所有的服务。
set和list元素用于分别用于定义set或者list语义的集合。
这些元素支持的属性有interface、filter、bean-name、availability和context-class-loader,和reference元素的语义相同。availability属性允许的值为mandatory和optional。
如果availability的值为optional,则表明允许没有匹配的服务,如果availability的值为mandatory,则表明必须至少有一个匹配服务,这样的引用称为“必需”的引用,从同一个bundle导出的服务(定义的服务bean),依赖于强制引用,当引用不可用时,它会自动被注销;当引用再次可用时,它会被自动重新注册。参见第9.2.2节“导入服务的可用性”。
list元素定义的bean的类型为java.util.List。set元素定义的bean的类型为java.util.Set。
| 注意 |
要确保Gemini Blueprint/Spring DM的集合注入的属性是类型兼容的(例如set元素到Set或Collection中),否则,容器会自动进行类型转换,将Gemini Blueprint/Spring DM托管的集合转换成普通的集合,失去了OSGi的动态性。 |
下面的例子定义了一个类型为List的bean,它包含所有支持EventListener接口的服务:
<list id="myEventListeners" interface="com.xyz.EventListener"/>
这个bean定义的集合中的成员由Spring动态管理。由于匹配的服务在服务注册表中注册或者注销,集合成员会保持实时更新。集合的每一个成员都对应的服务支持的且对bundle可见的服务接口。
Gemini Blueprint/Spring DM也支持有序集合,set和list都支持排序。
可以使用comparator-ref属性或者内嵌的comparator元素指定排序的顺序。comparator-ref属性用于引用一个实现了java.util.Comparator接口的bean。Comparator元素用于一个内联的bean。例如:
<set id="myServices" interface="com.xyz.MyService" comparator-ref="someComparator"/> <list id="myOtherServices" interface="com.xyz.OtherService"> <comparator> <beans:bean class="MyOtherServiceComparator"/> </comparator> </list> |
如果排序时使用的是自然序,而不是显式指定的comparator,你可以使用natural元素代替comparator。你需要指定自然序的基:基于服务应用,即遵循OSGi核心规范4 v4.1中定义的ServiceReference自然序,或者基于服务自己(服务必须实现Comparable接口)。
<list id="myServices" interface="com.xyz.MyService"> <comparator><natural basis="services"/></comparator> </list> <set id="myOtherServices"interface="com.xyz.OtherService"> <comparator><natural basis="service-references"/></comparator> </set> | ||
| 注意 | |
对于有序的set,会创建一个SortedSet实现对象。然而,因为JDK API没有提供专门的SortedList接口,有序的list只能实现List接口。 |
9.2.4.1. 贪婪代理
Gemini Blueprint/Spring DM服务集合导入的所有服务都是由interfaces属性声明的类发布的,并且是类型兼容的。然而,一些服务可能需要暴露额外与应用相关的类。
对于这些场景,Gemini Blueprint/Spring DM集合提供了专门的属性,称为greedy-proxying,这将导致创建导入服务发布的所有类的代理,对于用户导入bundle可见。这样,可以将导入的代理对象强制转换为不是由interface指定的类。例如,下面的list定义:
<list id="services" interface="com.xyz.SomeService" greedy-proxying="true"/>
你可以做如下的迭代(假设bundle导入的是MessageDispatcher类型):
for (Iterator iterator = services.iterator(); iterator.hasNext();) { SomeService service = (SomeService) iterator.next(); service.executeOperation(); // if the service implements an additional type // do something extra if (service instanceof MessageDispatcher) { ((MessageDispatcher)service).sendAckMessage(); } } | ||
| 注意 | |
在使用贪婪代理和instanceof之前,请考虑使用不同的接口和类,这样提供更好的多态性,更加面向对象。 |
9.2.4.2. 成员类型
从Spring DM 2.x/Gemini Blueprint开始,服务集合可以包含服务实例(默认),也可以包含服务引用。如果服务自身是不相关的,只是它们的属性和可用性是相关的,那后一个就很有用了。例如,为了跟踪服务引用,可以使用下面的配置:
<list id="services" interface="com.xyz.SomeService" member-type="service-reference"/>
注意这个集合包含本地的服务引用,客户端使用本地的服务引用能够获取到后端的服务(如果需要的话)。然而,在Gemini Blueprint/Spring DM中,这种使用场景是不鼓励的,因为可以让框架来跟踪服务,并直接获取有关的(本地)服务引用(参见9.2.3.10节“得到托管服务引用”。
9.2.4.3. 集合元素(list和set)属性
list和set元素支持reference元素的所说有属性,除了timeout属性。下面的表格总结了list和set元素的属性名、可能的值以及简短描述。
表9.4. <list>/<set>的属性
名字 | 值 | 描述 | ||
interface | 全限定类名 例如java.lang.Thread | 导出对象的全限定类名 | ||
filter | OSGi过滤表达式 例如(asynchronous-delivery=true) | OSGi过滤表达式,用于限制匹配服务 | ||
bean-name | 任何字符串 | 指定过滤表达式的方便的快捷方式。匹配bean-name属性的服务会自动发布。 | ||
context-class-loader | client service-provider unmanaged | 定义当调用服务引后端的服务上的操作时,如何管理上下文类加载器。默认值为client,意思是上下文类加载器只能看到这个bundle类路径黄总的资源。如果值为service-provider,意思是上下文类加载器嫩而过看到导出服务所在bundle类路径的资源。如果为unmanaged,意思是不管理上下文类加载器 | ||
availability | optional mandatory | 定义后端服务可用性的关系。如果没有指定,则默认的availability属性为'mandatory',意思是任何时候后端服务必须存在。如果为optional,则表明导入者可以没有后端服务 | ||
comparator-ref | 任何字符串 | 引用一个bean,作为集合的比较器。声明一个comparator,声明的集合就是有序的 | ||
greedy-proxying | true false | 表明是否为导入的OSGi服务创建指定类(false)或者所有类(true)的代理,并且对导入bundle是可见的。默认为false | ||
member-type | service-object service-reference | 表明引用集合中对象的类型。默认为 service-object,表明集合包含导入服务的代理;如果为service-reference ,则表明集合中包含的是匹配目标服务类型的 ServiceReference对象 |
下面的表格列出了comparator和natural子元素的属性:
表9.5. 集合的<comparator>属性
名字 | 值 | 描述 | |
basis | service service-reference | 表明按自然序排序的元素- service:只考虑服务实例;service-reference 只考虑服务的引用 |
9.2.4.4. list / set与OSGi的动态性
在应用上下文的生命周期内,集合中的OSGi服务会改变内容,因为他需要反映OSGI空间的状态。随着服务的注册和注销,它们会被添加到集合或从集合删除。
虽然reference声明会尝试在后端服务注销时查找替代服务,而集合简单将服务从集合中移除。与reference一样,服务集合可以具有指定的可用性。与reference不同的是,集合的中的内容可以被查询,无论它是否可用,以及持有多少服务。
与reference一样,强制的集合会触发依赖它的导出服务的注销。参见9.2.2.2节“服务导出者与服务导入者之间的关系”。
9.2.4.5. 迭代器合同和服务集合
遍历集合的推荐方式是使用迭代器。然而,由于OSGi服务时有时无,托管服务集合会相应调整。Gemini Blueprint/Spring DM会透明地更新用户持有的迭代器,这样就可以安全地遍历集合。此外,迭代器会反映集合的所有变化,即使是迭代器创建后(即在迭代过程中)发生变化。考虑这样一场景,集合在迭代开始后急剧收缩(例如大量OSGi服务关闭)。为了避免处理“死亡”服务引用,Gemini Blueprint/Spring DM不迭代集合的快照(快照可能不精确),而是收到服务事件更新集合,这样它们就能反映最新的集合状态,不管迭代是快还是慢。
请注意,服务更新只影响在事件发生后执行迭代的操作。迭代器已经返回服务不再更新,即使后端服务被注销。再加一点,如果调用注销的服务上的操作,就会抛出ServiceUnavailableException异常。
总之,虽然reference声明会搜索候补服务,如果后端服务被注销,但是服务集合不会替换返回给用户的注销的服务。然而,它会从集合中移除注销的服务,这样将来再迭代集合时就不会遇到它们。
请注意,迭代器合同保证next()方法总是顺从hashNext()执行的结果。
表9.6. 动态服务集合迭代合同
hasNext()返回的值 | next()行为 |
true | 总是返回非null值,即使当集合收缩了 |
false | 每一个迭代器的合同,抛出NoSuchElementException。即使有其它的服务添加到集合中,也是这样 |
上面的行为提供集合的一致视图,即使在迭代过程中集合的结构发生变化。为了简单的刷新迭代器,再次调用hasNext()方法。这会强制检查集合状态。
另外,在迭代有序集合过程中,任何元素添加到集合中都只会在迭代还没有传递到它们的排序点时是可见的。
9.2.5. 处理OSGi导入服务的动态性
listener元素在哪儿? |
从Spring DM 2.x/Gemini Blueprint开始,reference-listener替代listener元素,listener元素被废弃。这样做的主要原因是要与Blueprint规范的配置格式对齐,避免混淆listener声明的类型(基于service或者reference的)。注意listener元素依然支持。 |
无论你是否使用reference、set或者list,Spring动态模块都会管理后端的服务。然而,有些场景应用需要能够感知后端服务发生的更新。
这些需要感知后端服务绑定或者解绑的应用,可以使用内嵌reference-listener元素或者listener元素注册一个或多个监听器。在reference和set、list元素中都可以使用这些元素。服务导入者监听器和服务导出者监听器在许多方面都是类似的(参见9.1.10节 “服务注册和注销的生命周期”)。reference-listener元素引用一个bean(通过名字或者内联定义),这个bean会接收到服务绑定或者解绑的通知。如果这个bean实现了Gemini Blueprint/Spring DM的org.eclipse.gemini.blueprint.service.importer.OsgiServiceLifecycleListener接口,那么这个接口中的bind和unbind操作就会被调用。若不实现这个接口,必须命名自定义的bind和unbind的回调方法。
声明实现了OsgiServiceLifecycleListener接口的监听器的例子:
<reference id="someService" interface="com.xyz.MessageService"> <reference-listener ref="aListenerBean"/> </reference> |
声明一个内联的、自定义bind和unbind方法的监听器的例子:
<reference id="someService" interface="com.xyz.MessageService"> <reference-listener bind-method="onBind" unbind-method="onUnbind"> <beans:bean class="MyCustomListener"/> </reference-listener> </reference> |
如果这个监听器实现了OsgiServiceLifecycleListener接口,同时又指定了自定义的bind和unbind的操作,那么OsgiServiceLifecycleListener操作盒自定义的操作都会被执行,按照先后顺序。
自定义的bind和unbind方法的签名必须下面的签名之一:
public void anyMethodName(ServiceType service, Dictionary properties);
public void anyMethodName(ServiceType service, Map properties);
public void anyMethodName(ServiceReference ref);
这里的ServiceType可以使任意类型。请注意,bind和unbind回调只有当后端服务匹配声明的签名类型(ServiceType)时才会被调用。如果不管什么类型,你都想要调用这些回调,那么ServiceType要用java.lang.Object。
参数properties包含了服务注册需要的属性。
如果方法签名只有一个ServiceReference类型的参数,那么服务的ServiceReference 将会被传递给回调方法。
当listener用于reference声明时:
- bind回调在reference初始化绑定到后端服务时调用,或者后端服务被新的后端服务替换时调用。
- Unbind回调只有当当前的后端服务注销时调用,且没有可用的替换服务(即reference不满足了)。
当listener用于集合声明时(set或list):
- bind回调在新服务添加到集合中时调用。
- unbind回调在服务注销,从集合中移除时调用。.
注意,service集合没有服务重新绑定的概念:服务添加到集合或者从集合移除。
bind和undbind回调与OSGi服务修改事件是同步处理的,并且是在传递OSGi ServiceEvent事件的线程中调用的。
下面的表格列出了reference-listener子元素的可用的属性:
表9.7. OSGi <reference-listener>属性
名字 | 值 | 描述 |
ref | bean的名字引用 | 基于名字引用一个bean作为监听器 |
bind-method | 字符串,表示一个合法的方法名 | 当后端服务绑定时,调用的方法名 |
unbind-method | 字符串,表示一个合法的方法名 | 当后端服务解绑时,调用的方法名 |
9.2.6. Blueprint reference比较
类似于Gemini Blueprint/Spring DM,Blueprint容器提供了reference和list元素,功能与Blueprint/Spring DM中的完全相同。下表总结了Gemini Blueprint/Spring DM and Blueprint的可用配置选项:
表9.8. Spring DM / Blueprint服务导入配置比较
Gemini Blueprint/Spring DM | Blueprint |
Common Elements/Attributes |
|
interface | interface |
interfaces | - (multiple interfaces not supported) |
ref | ref |
filter | filter |
bean-name | component-name |
availability | availability |
context-class-loader | - |
<reference> |
|
timeout | timeout |
sticky | - (the importer is always sticky) |
<list> |
|
member-type | member-type |
comparator-ref | - |
greedy-proxying | - |
Blueprint和Gemini Blueprint/Spring DM的registration-listener声明时完全相同的,本节不再介绍。
9.3. 监听器和服务代理
虽然导入者的监听器提供了访问OSGi服务绑定的权限,但是注意给定的参数不是实际的服务,而是代理。这可能会有微小的副作用,尤其对于服务类名和标识。使用代理重新绑定的原因就是阻止监听器强引用服务(服务有可能随时消失)。跟踪特定服务的监听器不应该能依赖于实例相等性(==)。可以使用对象相等性(equals/hashcode),但前提是后端服务暴露了前述的方法(通常在发布的接口或者类中声明)。如果这些方法没有发布,代理会调用自己的方法,而不是目标方法。这是故意的,因为虽然代理尽快能的保持透明,但是由开发者决定期望的语义。
这样,(尤其对于reference的导入者),推荐跟踪服务接口和合同(不是标识),服务属性(参见org.osgi.framework.Constants#SERVICE_ID)或者通知(bind/unbind)。
9.4. 访问调用者的BundleContext
有时候,导入的服务想知道使用哪个bundle在使用它。为了支持这种场景,在Gemini Blueprint/Spring DM中,导入的服务通过LocalBundleContext类发布了导入bundle的BundleContext。每一次调用导入服务上的方法时,调用者的BundleContext就可以通过getInvokerBundleContext()访问。请小心使用这个类,因为它绑定了Gemini Blueprint/Spring DM API。
9.5. 导出者/导入这监听器最佳实践
如前所述,Gemini Blueprint/Spring DM导出者与导入者允许用监听器接收服务绑定、解绑、注册或者注销的通知。当使用监听器时,有一些指导建议:
- 在监听器中不要执行长耗时任务。如果必须执行耗时活动任务,需要用单独的线程来执行这个工作。监听器是同步调用的,所以越快越好。在监听器的操作会阻止发送其它的事件,OSGi服务会将活动挂起。
- 尽可能使用listener的自定义声明-不要将你的代码绑定到Gemini Blueprint/Spring DM API,不要强制使用特定的签名。
- 如果你发现在listener定义中重复声明bind/unbind方法,考虑使用Spring的bean定义继承来定义公共定义,公共定义可以服用并按需自定义。
- 偏好java.util.Map,而不是java.util.Dictionary。Map是接口,而Dictionary是被废弃了的抽象类。为了保留兼容性,Gemini Blueprint/Spring DM会将传递给监听器的Map实现强制转换为Dictionary(如果需要的话)。
- 使用重载方法时要小心:所有匹配特定服务类型的方法都会被调用,这可能不是希望的。考虑下面的监听器:
public class MyListener { void register(1 Object service, Map properties); void register(2 Collection dataService, Map properties); void register(3 SortedSet orderedDataService , Map properties); } |
1、Object类型-匹配所有的服务。这个方法永远会被调用。 |
9.5.1. 监听器和循环依赖
有一些场景导出者和导入者需要引用定义的bean:
<bean id="listener" class="cycle.Listener"> 1 <property name="target" ref="importer" /> 2 </bean> <osgi:reference id="importer" interface="SomeService"> 3 <osgi:listener bind-method="bind" ref="listener" /> 4 </osgi:reference> |
1、监听器bean |
上面的声明虽然合法,但是它让监听器和导入者互相依赖。为了创建导入者,就必须解析监听器,为了创建监听器,又必须获取(实例化和配置)导入者服务。需要破解这个循环,才能完全创建和配置bean。对于导出者和导入者来说,Gemini Blueprint/Spring DM都支持这种场景,。然而,如果监听器定义为内联的bean,循环将无法解析:
<osgi:reference id="importer" interface="SomeService"> <osgi:listener bind-method="bind"> <bean class="cycle.Listener"> <property name="target" ref="importer" /> </bean> </osgi:listener> </osgi:reference> |
1、OSGi服务导入者 |
bean和循环 |
循环依赖(A依赖于B,同时B依赖于A)增加了配置的复杂性,在大多数情况下,这都是设计问题。什么样的bean可以被创建和配置?虽然不是好的实践,但是当涉及单例时(因为实例可以被缓存起来),Spring容器仍努力尝试解析循环配置。然而这不并总是有效的,严重依赖特定的配置(bean类可以部分实例化吗?它依赖于特殊的接口吗?涉及到BeanPostProcessors吗?) |
上面的例子将会失败,因为服务bean不能够实例化,它依赖于监听器。之前看到过同样的循环,但是这种情况有点微妙且从容器的角度来看,有很大不同-声明的监听器为内嵌的或者内部的bean(因此没有bean id)。内部的bean与它们的父bean具有相同的生命周期。从定义上看,它们没有被容器跟踪,只是简单的按需创建。由于导入者不能部分创建,内嵌的监听器也不能缓存,容器就不会拆解循环,也不创建bean。虽然上面的两个配置是类似的,但是一个能工作,另一个却不能工作。这是另一个不要使用循环的原因,如非你真的必须用。
总之,如果你需要在监听器内持有导出者或者导入者的引用,要么将监听器声明为顶级的bean,要么考虑依赖查找。然而,后者需要额外的上下文信息,例如使用的BeanFactory,bean名字等,并且更加脆弱。
| 注意 |
给那些想对技术细节感兴趣的人。不管导出者还是导入者都不能部分实例化,因为它们依赖于应用上下文类加载器,而上下文类加载器是通过BeanClassLoaderAware访问的,BeanClassLoaderAware又依赖于内置的BeanPostProcessor,显然BeanPostProcessor只有在bean配置和实例化后才能应用。如果不需要ClassLoader,那么导出者或者导入者可以部分实例化,就支持上面的场景了。 |
9.6. 服务导入者的全局默认值
对于文件中声明的所有导入者,osgi命名空间提供了两个全局属性用于指定默认的行为。这样,当我们使用osgi命名空间来包装set、list和reference元素,你可以使用下面的属性:
- default-timeout:对于那些没有显示指定超时时间的导入者元素,可以用该属性指定的超时时间(单位毫秒)。例如:
<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" 1 osgi:default-timeout="5000"> 2 <reference id="someService" interface="com.xyz.AService"/> <reference id="someOtherService" interface="com.xyz.BService" 3 timeout="1000"/> 4 </beans:beans> |
1、声明osgi命名空间前缀。 |
- default-availability:对于那些没有显示指定可用性的导入者元素,可以用该属性指定默认的可用性。可能的值为optional和mandatory。Spring DM 1.x中使用的default-cardinality属性仍然可用,但是已经被废弃。考虑下面的例子:
<beans:beans xmlns="http://www.springframework.org/schema/osgi" 1 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:beans="http://www.springframework.org/schema/beans" 2 xmlns:osgi="http://www.springframework.org/schema/osgi" 3 osgi:default-availability="optional" 4 default-lazy-init="false"> 5 <reference id="someService" interface="com.xyz.AService"/> 6 <set id="someSetOfService" interface="com.xyz.BService"/> 7 <list id="anotherListOfServices" interface="com.xyz.CService" availability="mandatory"/> 8 </beans:beans> |
1、将Gemini Blueprint schema声明为默认的命名空间。 2、导入Spring框架的beans shema,命名空间关联为beans。 3、导入Gemini Blueprint schema,命名空间关联为osgi。由于需要为beans元素声明全部属性,为了避免模糊,Gemini Blueprint/Spring DM schema也指定一个前缀。 4、为根元素声明default-availability。如果没有设置,默认值为mandatory。本例中默认值为optional。注意osgi为添加到全局属性了。 5、beans元素属性不需要前缀(例如default-lazy-init),因为它们被声明为本地的,没有限定。 6、reference声明继承默认的availability值,因为它没有指定。 7、set声明继承默认的availability值,因为它没有指定。 8、list声明指定了availability值(mandatory),覆盖了默认值。 |
default-*属性允许简洁和简短声明,且易于传播变化(例如增加或者减少超时时间s)。