OSGI进阶:第七章 OSGi 最佳实践

 由  ValRay 发布

7. OSGi 最佳实践

7.1. 接口和实现分离为不同的Bundle

当接口和实现均处于同一个 Bundle 时,容易出现的问题就是如果要切换提供的 OSGi 服务的实现的话,就有些麻烦了,只能在调用该 OSGi 服务的组件中指定引用的服务的 target 来实现了,但如果接口和实现分离为不同的 Bundle 的话,对于提供的单一的 OSGi 服务(就是系统中只存在一个对于此 OSGi 服务的实现的组件)的切换就很方便了,对于存在多个实现同一 OSGi 服务的组件的情况也一样,在那种情况下尽管引用该 OSGi 服务的组件基本都会通过 target 过滤来获取自己想要的服务,但如果接口和 实现在同一个 Bundle 的话,就必须保证新的实现的 Bundle 中的 OSGi 服务的 target 属性必须采用不同的值,而不能使用同一个值。 举例来理解下上面的场景: 系统中有一个登录校验的服务,在该登录校验服务的 Bundle 中同时存在了一个登录校验服务实现的组件,该组件基于文件的方式实现登录的校验,并对外提供了登录校验服务,此时登录校验 Action 类位于另外的 Bundle 中,它引用了登录校验服务,现在要将登录校验改为使用数据库的方式来进行校验,实现的方法有以下两种: 修改目前的登录校验服务组件 这方法无疑不是很好的选择,如果将来又切换为基于文件的方式来校验呢,那岂不是又得修改这个组件的代码,另外的一个问题就是有些时候原有的代码是不能修改的。 新建登录校验服务组件,基于数据库方式实现 新建一个登录校验服务组件,该组件基于数据库方式来实现,此组件在对外提供 OSGi 服务时增加一个标识为数据库方式的属性值; 修改登录校验 Action,在其引用 OSGi 服务的部分增加 target 属性指定获取数据库方式的 OSGi 服务。 可见这种方式也不是好的选择,同样的涉及到了对原有代码的修改。 从上面的例子中可以看出,当 OSGi 服务的接口和实现位于同一 Bundle,对于系统的扩展和灵活性会造成很大的限制。 接下来看看当 OSGi 服务的接口和实现分离为不同的 Bundle 时上面场景的实现方法: 此时只需要新建一个数据库方式校验的登录校验 Bundle,在其中编写一个基于数据库方式实现登录校验的 OSGi Component,并对外提供登陆校验 OSGi 服务。 部署这个新的 Bundle 到 OSGi 应用中,同时停止原文件方式登录校验的 Bundle。 通过上面的方法就实现了登录校验服务的切换,可见当 OSGi 服务的接口和实现分离为不同的 Bundle 的时候,系统的灵活性和扩展性都得到了保证,因此其被列入 OSGi 的佳实践之一。

7.2. 保持系统动态性

动态性是 OSGi 系统中应该具备的,动态性的原则有两点,其实就是“即插即用、即删即无”原则: 不因为 Bundle 的卸载或不可用导致应用出现崩溃性的错误或无意义的入口 对于引用了的 OSGi 服务的 Component,应注意处理当服务不可用时的情况,同时应保证当 bundle 卸载时,其相应的功能入口(如网页上的链接、按钮或应用程序上的菜单等)也应相应的删除,避免出现无意义甚至会导致错误的功能入口。 就像例子中的留言板系统,在基于 OHSW 脚手架中的留言板系统当卸载新增留言模块时,相应的其功能入口也被卸载,而同样的如果卸载掉 Hibernate 封装模块,并不会造成留言列表模块的崩溃性错误,代替的是友好的错误提示。 而在重构原有留言板系统为 OSGi 应用时,即使卸载了新增留言的模块,留言列表的页面上新增留言的功能入口仍然存在,这就没有很好的保证系统的动态性。 当有新的 Bundle 安装时相应的功能入口或挂接应自动的完成 当新的 Bundle 安装到 OSGi 应用时,相应的其 OSGi 的服务等功能应立即生效,例如其功能的入口应自动的挂接到相应的地方等。

以上两点原则是在编写 OSGi Component 时应特别注意的,否则使用 OSGi 带来的好处也就大幅度的降低了,按照树状设计模式而言,就是当叶被折断时,树上就没有了这叶了,如果是枝被折断时,枝上的其他的枝和叶也相应的不在树上了,当树上长出了新的枝叶的时候,那么树也就自然的变的茂盛了,系统的功能也应变得更为强大。 保持系统的动态性是基于 OSGi 的初级实践者们很容易忽略的部分,因为原来实现系统的习惯都是静态化的实现,而实现的思维则要转化为实现的是动态化的系统,这需要一定的适应过程。 要实现“即插即用、即删即无”,推荐采用的实践方法是:

不强依赖其他 Bundle 的资源 意思是不在当前的 Bundle 中显示的直接使用其他 Bundle 的资源,如不在留言列表页面上显示的编写新增留言的功能链接等,这个可通过扩展点的方式来实现, 避免出现强依赖其他 Bundle 资源或功能的现象。

不强依赖任何 OSGi 服务 意思是不假设在当前 Bundle 的 Component 激活后,其中所引用的 OSGi 服务一直可用,这个可在代码中调用服务前先判断服务是否为 null 等方法来实现。 对于某些一定需要有依赖的 OSGi 服务存在的 Component,则可在 component 的描述文件中通过 cardinality 属性来控制,这样可以保证当必须依赖的 OSGi 均不存在时,component 进入 deactive 状态,如;对于不是必须依赖的 OSGi 服务,则可将其 cardinality 设置为’0..1’或’0..n’这样的方式,对于引用的这种 OSGi 服务,就必须在调用其前判断下此OSGi服务是否可用,如Component中对于LogService 的引用。

监听系统的动态的变化 意思是应动态的去监听可能会动态变化的部分,在具体的实现时可采用监听器、 Event、CM 以及 OSGi Declarative Services 的 bind、unbind 方法来实现,这样在 当系统发生动态的变化时,Component 或其他类都可以做出相应的动作,保持系统可动态的做出响应。

7.3. 搭建公司级的Bundle Respository

使用 OSGi 一个很大的意义就是它很大程度的提升了系统的可重用性,由于 OSGi 提供了规范的模块化和规范的模块交互机制,并具备了充分的语义,这样的话使得模块化级别的重用甚至是系统级的重用成为了可能。

OSGi 下一版本规范中很可能会纳入 Bundle Respository,Bundle Respository 和 Maven Respository 功能基本是一样的,只是 Bundle Respository 存放的是 Bundle,Bundle 通过标准的描述发布至 Bundle Respository 中,使得其他需要使用 Bundle 的同仁们可通 过 Bundle Respository 查找相应功能的 Bundle,目前已有的 Bundle Respository 有:

http://www2.osgi.org/Respository/HomePage

关于此 Respository 的使用,Peter Kriens 提供了一个很经典的录屏:

http://www.aqute.biz/Blog/20070703

建立公司级的Bundle Respository对于公司而言具备很大的意义,建立起Bundle Respository后,公司在进行每个项目之前都可通过Bundle Respository来寻找是否有相 似功能的Bundle,这样就可以快速的搭建起项目的脚手架了,这对于公司项目的积累和共享是非常有帮助的,而这也是在使用OSGi后能给公司带来的明显的帮助,同时 还可借助互联网上的各种公开的Bundle Respository,鼓励大家贡献Bundle给Bundle Respository ,增强业界的分享,提升系统的开发速度,减少重复劳动。

7.4. 创建共享library Bundle

在一个项目中,每个模块都会需要用到一些共同的第三方的 jar 包,按照正常的方式去搭建 Bundle 的话,需要在每个 Bundle 的 lib 中都放入其所需要的 jar 包文件,这样会造成的问题有: 所依赖的第三方库重复出现,造成后的 Bundle 文件过大; 各 Bundle 维护各自的第三方库,有可能会造成引用的第三方库版本不同; 解决这个问题的佳实践的方法是: 创建一个共享的第三方库的 Bundle;

将需要共享的第三方库的 jar 文件放入此 Bundle; 将其他 Bundle 需要的第三方库的 jar 的 package 对外 Export;

需要使用第三方库的 Bundle 通过 import package 的方式引用所需的 package。

7.5. 小化依赖(Minimize Dependencies)

这个佳实践来源于Peter和BJ的OSGi best practices文档 。

小化依赖的目的是要尽量的减少 Bundle 之间的依赖,避免出现安装一个 Bundle 时要安装 N 多其依赖的 Bundle 的现象。 为实现小化依赖,Peter 和 BJ 提出了以下几点佳的实践:

使用 Import-Package 代替 Require-Bundle 只有在需要使用其他 Bundle 提供的资源文件时才采用 Require-Bundle 的方式,而在其他情况下应该都使用 Import-Package 去引用其他 Bundle 提供的 package,从而使用其他 Bundle 的功能。

使用版本范围控制 在引用的 package 中尽量指定版本范围,以减少运行时 OSGi 过多的判断。

设计 Bundle 在设计 Bundle 应遵循低耦合,高内聚的原则,不要把和 Bundle 不相关的功能放入到 Bundle 中。

7.6. 避免启动顺序依赖(Avoid Start Ordering Dependencies)

这个佳实践来源于 Peter 和 BJ 的 OSGi best practices 文档。

启动顺序依赖是指系统中 bundle 的启动依赖于其他 bundle 的启动,也就是说如依赖的 bundle 未启动时,启动该 bundle 的话会造成错误,这是 OSGi 初学者很容易犯的错误。 在任何 Bundle 的启动中都不应该对别的 Bundle 造成强依赖,应该遵循动态性原则,尤其是引用了其他 Bundle 提供的服务的时候,例如下面的例子:

A Bundle 的 Activator 中的 activate 方法是这么写的:

LoginService service=context.getService(context.getServiceReference (LoginService.class.getName())); service.init();

像上面这样的写法就使得提供 LoginService 实现的 Bundle 必须在 A Bundle 之前启动,否则在启动时就会造成 NullPointerException 错误。

启动顺序依赖除了会造成上面的问题,还有可能造成的问题就是启动时间过长,因为启动顺序依赖也就意味着在启动的时候就要做很多的工作了。

Peter 和 BJ 提出了以下的一些佳实践来避免启动顺序依赖的问题: 不要在初始化 Bundle 时引用 OSGi 服务; 使用 ServiceTracker 去动态的获取所需引用的 OSGi 服务的状态; 使用 DS 或 Spring-OSGi 自动的获取 OSGi 服务的状态。

查看评论