使用Blueprint管理模块的加载、服务依赖和配置
本文来自:http://www.deepsdn.com/archives/36
什么是blueprint?
Blueprint 是 OSGi Service Platform Enterprise Specification 标准的一 部分,被设计用来为OSGi容器提供依赖注入的框架. 它来源于Spring DM,因此它们很类似. Karaf中包括一个blueprint的实现,即 (the Apache Aries blueprint 实现).
Blueprint是以xml文档来构建应用,但它也有采用Annotation的方式,我们在此只介绍xml的方式。
在Bundle里,这个xml默认的位置在OSGI-INF/blueprint下,也可以在manifest.mf里指定其它位置上的xml文档。
当一个包含blueprint xml文档的bundle install并resolved,并且active后,Aries blueprint container就会开始解析这个文档。
在处理这些xml文档的过程中,bundle还会有个blueprint的状态,区别于bundle的状态。这些状态包括Graceperiod,Created,Fail。其中Created就是blueprint container已经解析完文档,并且文档中mandatory的依赖都得到满足了,这时blueprint的应用已经组装完毕。而Graceperiod则是应用正在组装中。Fail是当blueprint无法解析xml文档或者是mandatory的依赖在超时时间内未能获得满足。
在Blueprint里,还可以发布和组装OSGI service。
blueprint xml文档,有四种主要元素: bean, service, reference and reference-list:
- bean – 用来描述创建Java实例的元素,可以指定实例初始化的类名,构造方法,构造方法的入参及属性.
- service – 把bean发布为OSGi service
- reference – 通过接口名引用一个OSGi service,可以指定一个特定的属性过滤器
- reference-list – 通过接口名引用多个 OSGi services ,可以指定一个特定的属性过滤器
1.引用基础的MD-SAL binging服务,比如DataBroker, RpcProviderRegistry, and NotificationPublishService
<?xml version="1.0" encoding="UTF-8"?> <blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0" xmlns:odl="http://opendaylight.org/xmlns/blueprint/v1.0.0" odl:use-default-for-reference-types="true"> <reference id="dataBroker" interface="org.opendaylight.controller.md.sal.binding.api.DataBroker" odl:type="default"/> <reference id="rpcRegistry" interface="org.opendaylight.controller.sal.binding.api.RpcProviderRegistry"/> <reference id="notificationService" interface="org.opendaylight.controller.md.sal.binding.api.NotificationPublishService"/> </blueprint>
对于一个接口有多个实现的,odl:type可以用来确定某个具体实现,比如对于DataBroker,有一个默认实现和一个特殊实现PingPongDataBroker,如果不明确指定type,则获取的服务实现是不确定的,如果想要某个具体实现,必须指定type,odl:type=”default”表示默认实现,odl:type=”pingpong”表示PingPongDataBroker实现。
服务获取的动态性
在blueprint内部,reference 元素为服务实例创建了一个动态代理. 之所以如此做,就是考虑到OSGi 服务的动态本质,所有服务实现都可能因为bundle的动态启停,重启而生效或者失效. 当然,实际上,在像Opendaylight这样具有几百个bundle的大规模的平台里,真正实现完全的动态性是存在一定难度的(but that’s another topic).
在bundle启动时,blueprint container 会首先等待依赖的服务得到满足, 默认的等待超时时间是 5 分钟.如果超时,blueprint container会马上把bundle状态标记为失败. 另外,如果某个服务在启动后状态变为不可用,再调用该服务时,该调用将会被阻塞,直到新的服务实例可用或者超时. 如果服务调用超时,系统会抛出一个运行时异常. 这样就可以在应用代码里做相应处理以支持bundle的动态更新.
2.通过bean初始化业务逻辑类
<?xml version="1.0" encoding="UTF-8"?> <blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0" xmlns:odl="http://opendaylight.org/xmlns/blueprint/v1.0.0" odl:use-default-for-reference-types="true"> <reference id="dataBroker" interface="org.opendaylight.controller.md.sal.binding.api.DataBroker"/> <bean id="foo" class="org.opendaylight.app.Foo" init-method="init" destroy-method="close"> <property name="dataBroker" ref="dataBroker"/> </bean> <bean id="bar" class="org.opendaylight.app.Bar"> <argument ref="foo"/> </bean> </blueprint> 3. 发布服务
<?xml version="1.0" encoding="UTF-8"?> <blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0" xmlns:odl="http://opendaylight.org/xmlns/blueprint/v1.0.0"> <bean id="fooImpl" class="org.opendaylight.app.FooImpl"> <!-- constructor args --> </bean> <service ref="fooImpl" interface="org.opendaylight.app.Foo" odl:type="default"/> </blueprint>
MD-SAL blueprint扩展
odl mdsal对blueprint的扩展(xmlns:odl=”http://opendaylight.org/xmlns/blueprint/v1.0.0″)
i. Global RPC注册
<?xml version="1.0" encoding="UTF-8"?> <blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0" xmlns:odl="http://opendaylight.org/xmlns/blueprint/v1.0.0"> <bean id="fooRpcService" class="org.opendaylight.app.FooRpcServiceImpl"> <!-- constructor args --> </bean> <odl:rpc-implementation ref="fooRpcService"/> </blueprint>
ii. Global RPC的获取
<?xml version="1.0" encoding="UTF-8"?> <blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0" xmlns:odl="http://opendaylight.org/xmlns/blueprint/v1.0.0"> <odl:rpc-service id="fooRpcService" interface="org.opendaylight.app.FooRpcService"/> <bean id="bar" class="org.opendaylight.app.Bar"> <argument ref="fooRpcService"/> </bean> </blueprint>
iii. Routed RPC的注册
<?xml version="1.0" encoding="UTF-8"?> <blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0" xmlns:odl="http://opendaylight.org/xmlns/blueprint/v1.0.0"> <bean id="fooRoutedRpcService" class="org.opendaylight.app.FooRoutedRpcServiceImpl"> <!-- constructor args --> </bean> <odl:routed-rpc-implementation id="fooRoutedRpcServiceReg" ref="fooRoutedRpcService"/> <bean id="bar" class="org.opendaylight.app.Bar"> <argument ref="fooRoutedRpcServiceReg"/> </bean> </blueprint>
iv. NotificationListenser的注册
<?xml version="1.0" encoding="UTF-8"?> <blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0" xmlns:odl="http://opendaylight.org/xmlns/blueprint/v1.0.0"> <bean id="fooListener" class="org.opendaylight.app.FooNotificationListener"> <!-- constructor args --> </bean> <odl:notification-listener ref="fooListener"/> </blueprint>
还有一个需要了解的知识是blueprint的配置管理,可以直接使用blueprint的ConfigAdmin来配置某些本地参数,配置项是写在etc/*.cfg文件里的blueprint xml里要写上对namespace.
“http://aries.apache.org/blueprint/xmlns/blueprint-cm/v1.1.0″的使用,如下
<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0" xmlns:odl="http://opendaylight.org/xmlns/blueprint/v1.0.0" xmlns:cm="http://aries.apache.org/blueprint/xmlns/blueprint-cm/v1.1.0" >
<cm:property-placeholder persistent-id="org.opendaylight.foo" update-strategy="none"> <cm:default-properties> <cm:property name="max-retries" value="5"/> </cm:default-properties> </cm:property-placeholder> <bean id="foo" class="org.opendaylight.app.Foo"> <property name="maxRetries" value="${max-retries}"/> </bean> </blueprint>
也可以通过ODL的datastore实现集群内的全局配置
Opendaylight实现了一个不lueprint扩展,clustered-app-config,它可以令应用方便使用yang定义的配置数据,当然,这些配置必须定义在yang的顶层container或list内.
标签clustered-app-config从MD-SAL datastore里读取应用配置数据并封装配置数据为bean,这些配置bean具有与OSGi服务同样的依赖方式。如果配置数据不能从datastore读取并初始化(比如ODL 的datastore还没启动好),blueperint将会一直等待配置数据的依赖直至超时失败。
clustered-app-config元素内部注册了 ClusteredDataTreeChangeListener 监听器,以便在配置数据变化或者删除时,重启blueprint container.
使用 YANG container 实现应用配置
像下面这样定义一个 yang顶层 container:
module my-app-config { yang-version 1; namespace "urn:opendaylight:myapp:config"; prefix my-app-config; description "Configuration for ..."; revision "2016-06-24" { description "Initial revision."; } container my-config { leaf id { type string; mandatory true; } leaf connection-timeout { type uint16; default 3000; } } }
在你的blueprint配置XML里, 添加 clustered-app-config 标签,该标签的binding-class属性设置为由my-config这个container生成的带包路径的Java interface名称.
<odl:clustered-app-config id="myConfig" binding-class="org.opendaylight.yang.gen.v1.urn.opendaylight.myapp.config.rev160624.MyConfig"> </odl:clustered-app-config>
clustered-app-config 元素从MD-SAL data store里获取数据,生成绑定的DataObject对象bean(这里就是MyConfig的对象),该bean可以被注入到其他bean里。如果datastore里该数据为空,则由yang模型里默认值生成一个bean实例,在上面例子里,会创建一个connection-timeout等于3000,id为null的MyConfig实例
如果不想用yang里定义的默认值,你可以自己通过default-config子标签在blueprint xml提供默认值..
<odl:clustered-app-config id="myConfig" binding-class="org.opendaylight.yang.gen.v1.urn.opendaylight.myapp.config.rev160624.MyConfig"> <odl:default-config><![CDATA[ <my-config xmlns="urn:opendaylight:myapp:config"> <id>foo</id> </my-config> ]]></odl:default-config> </odl:clustered-app-config>
这里,我们对id字段提供了一个默认值,default-config元素里包含了yang定义的数据的XML表示(包括namespace),数据体用 CDATA段封装,以让blueprint container不把这部分数据作为blueprint标记来处理,这部分的数据表示形式与通过RESTCONF设置datastore时的XML格式一致.
默认值也可以通过外部文件指定. 在自动化测试脚本中,这非常有用,而且,你能在控制器没有运行的情况下方便的修改配置。 clustered-app-config 将在目录etc/opendaylight/datastore/initial/config下查找形如 <yang module name>_<container name>.xml 的文件. 比如,上面的例子,就会查找etc/opendaylight/datastore/initial/config/my-app-config_my-config.xml. XML文件名也可以用属性default-config-file-name明确指定.
注意: 默认值只是在datastore里没有数据时的初始值.一旦在datastore里设置了值,默认值就没用了.
如果是在同一个文件里,可以直接把MyConfig通过bean id注入到业务逻辑的bean.
<?xml version="1.0" encoding="UTF-8"?> <blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0" xmlns:odl="http://opendaylight.org/xmlns/blueprint/v1.0.0"> <odl:clustered-app-config id="myConfig" binding-class="org.opendaylight.yang.gen.v1.urn.opendaylight.myapp.config.rev160624.MyConfig"> </odl:clustered-app-config> <bean id="foo" class="org.opendaylight.myapp.Foo"> <argument ref="myConfig"/> </bean> </blueprint>
org.opendaylight.myapp.Foo类的构造方法入参就是类org.opendaylight.yang.gen.v1.urn.opendaylight.myapp.config.rev160624.MyConfig.
你可能还想直接把container里某个leaf字段的值注入到业务逻辑bean,而并不是整个container对象. 比如, 我们定义一个类 org.opendaylight.myapp.Bar with ,在构造方法里希望能把MyConfig里的id和connection-timeout传进来.
public class Bar { public Bar(String id, int connectionTimeout) { } }
这可以通过为构造方法的入参定义内部bean来实现,例子如下:
<bean id="foo" class="org.opendaylight.myapp.Bar"> <argument> <bean factory-ref="myConfig" factory-method="getId" /> </argument> <argument> <bean factory-ref="myConfig" factory-method="getConnectionTimeout" /> </argument> </bean>
使用 YANG list 实现配置
如果你想同时初始化某个组件的多个实例, 每个都有自己的单独的配置, 不需要为每一个实例定义特定的container, 使用顶层的 list 元素就能实现.
我们象下面这样定义一个顶层的 list:
module my-component-config { yang-version 1; namespace "urn:opendaylight:myapp:config"; prefix my-component-config; description "Configuration for ..."; revision "2016-06-24" { description "Initial revision."; } list my-component-config { key "instance-name"; leaf instance-name { type string; } leaf connection-timeout { type uint16; default 3000; } } }
元素clustered-app-config 支持设置一个类型为type的 list key. 这个 key唯一标示一个实例. 通过属性list-key-value指定he instance value must be specified via the list-key-value attribute.
<odl:clustered-app-config id="myComponentConfig1" binding-class="org.opendaylight.yang.gen.v1.urn.opendaylight.myapp.config.rev160624.MyComponentConfig" list-key-value="instance1"> <odl:default-config><![CDATA[ <my-component-config xmlns="urn:opendaylight:myapp:config"> <instance-name>instance1</instance-name> <connection-timeout>1000</connection-timeout> </my-component-config> ]]></odl:default-config> </odl:clustered-app-config> <bean id="foo1" class="org.opendaylight.myapp.Foo"> <argument ref="myComponentConfig1"/> </bean> <odl:clustered-app-config id="myComponentConfig2" binding-class="org.opendaylight.yang.gen.v1.urn.opendaylight.myapp.config.rev160624.MyComponentConfig" list-key-value="instance2"> <odl:default-config><![CDATA[ <my-component-config xmlns="urn:opendaylight:myapp:config"> <instance-name>instance2</instance-name> <connection-timeout>2000</connection-timeout> </my-component-config> ]]></odl:default-config> </odl:clustered-app-config> <bean id="foo2" class="org.opendaylight.myapp.Foo"> <argument ref="myComponentConfig2"/> </bean>
这里我们定义了两个配置,每个对应一个org.opendaylight.myapp.Foo的实例。
我们也为每个配置分别设置了默认值,对于yang list元素,在XML中,必须明确使用 list-key-value对list的key赋值。