第11章 纲要服务
OSGi服务平台服务纲要规范定义了OSGi实现可能支持的大量额外服务。Gemini Blueprint支持额外的"compendium"命名空间,以集成这些服务。按照惯例,这个命名空间的前缀使用osgix:
<?xmlversion="1.0"encoding="UTF-8"?> <beansxmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bp="http://www.osgi.org/xmlns/blueprint/v1.0.0" xmlns:context="http://www.springframework.org/schema/context" xmlns:util="http://www.springframework.org/schema/util" xmlns:task="http://www.springframework.org/schema/task" xmlns:compendium="http://www.eclipse.org/gemini/blueprint/schema/blueprint-compendium"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.2.xsd http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.2.xsd http://www.osgi.org/xmlns/blueprint/v1.0.0 http://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd http://www.eclipse.org/gemini/blueprint/schema/blueprint-compendium http://www.eclipse.org/gemini/blueprint/schema/blueprint-compendium/gemini-blueprint-compendium.xsd">
<!-- use the OSGi namespace elements directly --> <serviceid="simpleServiceOsgi"ref="simpleService" interface="org.xyz.MyService"/>
<!-- qualify compendium namespace elements --> <compendium:cm-propertiesid="cm"persistent-id="com.xyz.myapp"/> </beans:beans> |
| 纲要命名空间声明(绑定到osgix前缀) |
| Schema位置(命名空间的URI) |
| 纲要命名空间使用的XML模式 |
目前这个命名空间提供对配置管理服务的支持。未来的版本可能会增加其他的纲要服务。
11.1. 配置管理
最重要的纲要服务之一就是配置管理服务(Configuration Admin),正如它的名字一样,通过OSGi服务注册表对感兴趣的bundle进行配置。Gemini Blueprint 提供对管理服务(CM)专门的支持,允许以声明的方式消费和注入配置数据。
11.1.1. 将配置服务条目以属性方式暴露
CM作为配置源的最简单的形式就是词典(Dictionary,它的键总是字符串)。在CM中,Gemini Blueprint通过cm-properties元素将条目以Properties对象暴露。最小的声明可能如下所示:
<osgix:cm-properties id="ds.cfg"persistent-id="data.source.office.1"/>
上面的配置,将配置管理的属性data.source.office.1,作为一个bean ds.cfg暴露给CM。
| 注意 |
persistent-id属性必须引用一个OSGi ManagedService的persistent-id, 指定工厂持久id,引用ManagedServiceFactory是一个配置错误。 |
熟悉Spring的util模式的人可能会发现<osgi:cm-properties/>元素类似于<util:properties/>。
如果配置字典中不包含指定key的条目,可以指定默认的属性值。它的声明类似于Spring beans命名空间中的props元素:
<?xmlversion="1.0"encoding="UTF-8"?> <beansxmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bp="http://www.osgi.org/xmlns/blueprint/v1.0.0" xmlns:context="http://www.springframework.org/schema/context" xmlns:util="http://www.springframework.org/schema/util" xmlns:task="http://www.springframework.org/schema/task" xmlns:compendium="http://www.eclipse.org/gemini/blueprint/schema/blueprint-compendium"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.2.xsd http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.2.xsd http://www.osgi.org/xmlns/blueprint/v1.0.0 http://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd http://www.eclipse.org/gemini/blueprint/schema/blueprint-compendium http://www.eclipse.org/gemini/blueprint/schema/blueprint-compendium/gemini-blueprint-compendium.xsd">
<compendium:cm-propertiesid="cfg.with.defaults"persistent-id="data.source.office.2"> <beans:propkey="host">localhost</beans:prop> <beans:propkey="port">3306</beans:prop> </compendium:cm-properties> |
默认情况下,配置管理条目中的属性值会被局部的属性值覆盖。因此,对于前面的例子,如果data.source.office.2配置包含host条目,它的值会覆盖局部定义的localhost。如果不希望发生这样的行为,使用local-override属性(默认为false))恢复合并算法,强制局部属性覆盖CM的条目。
由于cm-properties将CM的条目以Properties方式暴露,它可以使用Spring的PropertyPlaceholderConfigurer和PropertyOverrideConfigurer将外部化和自定义特定环境的属性:
<?xmlversion="1.0"encoding="UTF-8"?> <beansxmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:bp="http://www.osgi.org/xmlns/blueprint/v1.0.0" xmlns:compendium="http://www.eclipse.org/gemini/blueprint/schema/blueprint-compendium" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd http://www.osgi.org/xmlns/blueprint/v1.0.0 http://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.2.xsd http://www.eclipse.org/gemini/blueprint/schema/blueprint-compendium http://www.eclipse.org/gemini/blueprint/schema/blueprint-compendium/gemini-blueprint-compendium.xsd">
<!-- Configuration Admin entry --> <compendium:cm-propertiesid="cmProps"persistent-id="com.xyz.myapp"> <propkey="host">localhost</prop> </compendium:cm-properties>
<!-- placeholder configurer --> <context:property-placeholderproperties-ref="cmProps"/>
<beanid="dataSource" ...> <propertyname="host"value="${host}"/> <propertyname="timeout"value="${timeout}"/> </bean> </beans> |
cm-properties不会反映任何后续配置管理API对该条目的修改。即,一旦它被解析,cm-properties就会保持不变,不管这个CM条目进行什么修改。
11.1.2.托管属性
基于配置服务条目,Gemini Blueprint通过名字自动装配特定bean的属性。要使用这个特性,需要在bean定义内定义一个嵌入的managed-properties属性:
<bean id="managedComponent"class="MessageTank">
<osgix:managed-properties persistent-id="com.xyz.messageservice"/>
</bean>
对于通过CM和特定persistent-id存储在字典中的每一个key,如果bean类具有匹配的名字的属性(遵循JavaBean约定),那么这个组件属性被依赖注入CM中key对应的值。如果上面例子中的一些类定义如下所示:
publicclass MessageTank { privateintamount; publicint getAmount() { returnthis.amount; } publicvoid setAmount(intamount) { this.amount = amount; } } |
在pid com.xyz.messageservice下存储的配置字典中包含一个条目amount=200,那么在配置过程中,就会调用这个bean实例的setAmount方法,传入值200。
如果属性值同时在配置服务的字典和组件元素的property元素声明中同时定义,那么CM中的值优先使用:
<bean id="managedComponent"class="MessageTank">
<osgix:managed-properties persistent-id="com.xyz.messageservice"/>
<property name="amount"value="100"/>
<property name="threshold"value="500"/>
</bean>
因此,通过property元素指定的属性值可以作为默认值,如果CM中没有的话。
| 注意 |
不要在多个bundle或定义中共享persistent-id (PID),因为它们中只有一个能接收到通知。managed-properties依赖org.osgi.service.cm.ManagedService合同,这个合同授权每一个ManagedService实例必须由它自己的PID唯一标识。请参见配置服务规范,尤其是第104.3和104.5节。 |
11.1.2.1. 配置服务运行时更新
配置服务的强大特定就是能够在运行时更新和删除条目。即,CM中存储的配置数据在bean创建完成之后,可以被更新。默认情况下,任何在创建之后的更新都会被忽略。然而,可以配置managed-properties元素的autowire-on-update和update-method属性来接收配置更新。
update-method指定当配置数据发生更新时调用bean的哪个方法。更新方法必须具有如下的签名:
public void anyMethodName(Map properties)
public void anyMethodName(Map<String,?> properties); // for Java 5
如果autowire-on-update设置为true(默认为false),那么容器会在每一次更新发生时自动装配目标bean。如果autowire-on-update和update-method都指定了,那么自动装配过程会优先进行。对于自动装配,组件类必须为组件属性提供setter方法。考虑下面的类定义:
publicclass ContainerManagedBean { // 将被重新注入(因为它由setter方法) private Integer integer; // 不会被注入(不存在setter方法) private Long waitTime;
publicvoid setInteger(Integer integer) { this.integer = integer; } } |
publicclass SelfManagedBean {
// 更新回调 publicvoid updateCallback(Mapproperties) { System.out.println("Received properties " + properties); System.out.println("Props can be used as a Dictionary " + properties); // 更多的工作... } } |
和配置:
<bean id="containerManaged"class="ContainerManagedBean"> <osgix:managed-propertiespersistent-id="labX"autowire-on-update="true"/> <propertyname="integer"value="23"/> </bean> <beanid="beanManaged"class="SelfManagedBean"> <osgix:managed-propertiespersistent-id="labY"update-method="updateCallback"/> </bean> |
对CM条目labX的任何更新都会被自动注入到已经存在的containerManaged bean实例中,而labY更新会被传递到updateCallback方法。
更新选项在下面的表格进行了总结:
表11.1.托管的属性更新选项
autowire-on-update | update-method | 行为 |
true | 可选,自动装配后调用 | 用更新中的属性重新注入bean属性。重新注入需要锁定bean实例(同步指令)。如果锁定和重新注入策略不合适,则只考虑使用update-method方法。 |
false (默认) | 可选 | 调用bean上的update-method回调,传入更新后的配置(传入一个Map对象,且可以安全的强转为一个Dictionary)。执行不锁定。 |
11.1.3. 托管服务工厂
配置管理服务支持“托管服务工厂”的概念(参见纲要规范第104.6节)。托管服务工厂由工厂pid唯一标识,通过工厂pid允许多个配置对象与工厂关联。与这个工厂有关的配置对象可以随时添加和移除。工厂的主要意图就是为每一个配置创建一个OSGi服务:添加一个配置条目会注册一个新的OSGi服务,移除一个配置条目就会注销一个OSGI服务。Gemini Blueprint提供了managed-service-factory元素来支持“托管服务工厂”的概念。一旦定义了这元素,与工厂pid有关的配置会自动创建或移除bean实例,这些bean实例基于模块bean和CM配置会在OSGi空间注册或注销。
这听起来好像有点复杂,那么实际上它很简单,我们来看一个简单的例子:
<osgix:managed-service-factory id="simple-msf"
factory-pid="com.xyz.messageservice"
auto-export="all-classes">
<bean class="com.xyz.MessageTank"/>
</osgix:managed-service-factory>
| 工厂持久id(pid) |
| 快捷标志,用于确定哪些接口会作为OSGi服务导出 |
| Bean定义模板。对于每一个检测到的配置,则使用bean定义模板持久一个新的服务 |
在它最简单的形式中,managed-service-factory需要一个工厂的factory-pid,用于模板的bean定义和一些将bean实例发布为服务的信息。上面的定义主要就是指导Gemini Blueprint监视给定的工厂pid(通过专门的ManagedServiceFactory实现,参见纲要规范了解更多信息),对于与工厂pid有关的所有配置对象,创建一个新的、匿名的嵌入bean实例,并作为OSGi服务导出。这些bean实例的生命周期绑定到与配置对象有关的生命周期上。如果天机了一个新配置,则创建一个新bean并导出。如果删除了一个配置对象或与工厂pid分离,那么相应的bean实例也会被销毁。
在很多方面,managed-service-factory扮演了一个专门的服务出口商,类似于service元素,但是支持托管属性。实际上,managed-service-factory有许多服务的属性,类似于managed-properties属性,会表明bean如何被导出。
这些属性在下表中列出:
表11.2. 托管服务工厂选项
名字 | 值 | 描述 | |||
interface | 全限定类名(例如java.lang.Thread) | 导入对象的全限定类名 | |||
context-class-loader | unmanaged | service-provider | 当执行导出服务的操作时,上下文类加载器如何托管。默认值为unmanaged,即不托管上下文类加载器。service-provider保证上下文类加载器对于所有导出服务的bundle中的资源都是可见的 | ||
auto-export | disabled (默认) | interfaces | class-hierarchy | all-classes | 允许Spring自动管理这个服务服务发布的服务接口。默认情况下,这个工具是disabled。 如果为interfaces,则导出服务发布所有Java接口。如果为class-hierarchy,则发布导出服务所有继承层次的Java类。如果为all-classes,则发布所有的类和接口 |
autowire-on-update | false (默认) | true | 每次当更新发生时,容器是否应该自动装配目标bean。当指定为true时,容器会自动装配重新设置了属性的bean实例。如果也使用了update-method属性,自动装配过程优先执行 | ||
update-method | none (默认) | 某个方法 | 当配置数据发生更新时,就会调用更新方法。允许目标bean自己处理更新信息。如果使用了autowire-on-update,那么在自动装配之后,再调用update-method |
类似于service元素,当服务注册或注销时,接口和监听器都可以声明被通知。要了解更多信息。请参见第9.1.3节“控制导出服务发布的接口”和第9.1.10节“服务注册和注销的生命周期”。
现在managed-service-factory选项已经解释清楚了,让我们看一个更加复杂的配置:
<bean id="queueTracker"class="org.xyz.queue.QueueTracker"/> <osgix:managed-service-factory id="data-msf" factory-pid="org.xyz.labX" autowire-on-update="true" update-method="refresh"> <osgix:interfaces> <value>java.util.Collection</value> <value>java.util.Queue</value> </osgix:interfaces> <osgix:registration-listener ref="queueTracker" registration-method="track" unregistration-method="untrack"/> <bean class="com.xyz.ResizableQueue"> <property name="size"value="100"/> <property name="concurrency"value="10"/> <property name="fair"value="false"/> </bean> </osgix:managed-service-factory> |
| ManagedServiceFactory工厂持久id |
| 当配置更新时,Gemini Blueprint是否自动装配bean |
| 自动接入之后调用的方法 |
| 嵌入bean将这些interfaces发布为OSGi服务 |
| 当服务(基于CM配置)注册或注销时,需要通知的监听器 |
| 自定义服务注册方法(可选) |
| 自定义服务注销方法(可选) |
| Bean定义模板 |
上面的例子,为org.xyz.labX工厂id下的每一个配置条目创建了虚构的ResizeableQueue实例。每一个实例都为size、concurrency和fair参数赋了默认值。然而,就像managed-properties一样,在bean创建期间,来自配置管理的值会通过名字注入,可能会覆盖已有的设置。一旦创建和配置,每一个嵌入的、匿名的bean实例都以java.util.Collection和java.util.Queue接口注册为OSGi服务。OSGi服务生命周期被注册监听器监视,即bean queueTracker。最后,由于指定了autowire-on-update和update-method属性,对于每一个CM配置的更新都会引发容器用新设置的属性自动装配有关的bean实例。之后,就会调用bean上面的刷新回调。
11.1.4. 直接访问配置数据
与保存在持久id和工厂持久id下面的配置数据一起工作最简单的方式,就是注册一个实现了ManagedService或者ManagedServiceFactory接口的服务。指定一个你感兴趣的pid作为服务属性(要了解更多信息,请参阅OSGi纲要规范的配置管理章节)。例如:
<osgi:service interface="org.osgi.service.cm.ManagedService"ref="myManagedService">
<osgi:service-properties>
<entry key="service.pid"value="my.managed.service.pid"/>
</osgi:service-properties>
</osgi:service>
<bean id="myManagedService"class="com.xyz.MyManagedService"/>