在OSGi中,服务是实现bundle间交互和应用灵活性的基石。借助于服务,我们能够降低bundle之间的耦合,更加有利于软件的重用,通过强调面向接口编程,可以提高软件的灵活性与设计水平。
传统方式下,我们注册服务都是在bundle的激活器(Activator)中使用BundleContext.registerService()方法完成的。而服务的获取需要通过BundleContext.getServiceReference()获取ServiceReference实例,进而使用BundleContext.getService()得到真正的服务实例。这种方式虽然能够完成服务的发布与使用,但是有一定的不足,具体来讲:
产生较多的样板式代码。OSGi的bundle是动态化的,伴随着bundle的安装和卸载,它所发布的服务也会动态地处于可用或不可用的状态,因此每次使用服务的时候,我们都需要借助BundleContext对象去服务注册中心查找,而不能通过一次查找,一劳永逸地持有服务对象的引用。尽管有ServiceListener和ServiceTracker帮助我们监听和跟踪服务的状态,但是总体而言这种方式较为繁琐且容易出错。
影响启动时间,服务在激活器中注册时,需要实例化所有要发布的服务对象,因为激活器的start()方法是同步调用的,所以会影响到整个应用的启动时间。
大内存的占用,在激活器中注册服务时,我们需要实例化所有的服务对象,但是这些服务在应用运行期间,并不一定会用到,这在无形中加大了内存的占用。
PI依赖引起的平台侵入性。使用传统方式注册和使用服务,会用到大量的OSGi API,从而产生与OSGi平台的耦合,如果要将代码复用到非OSGi场景之中,需要较多的重构工作。
OSGi通过声明式服务(Declarative Service)以及Blueprint规范来解决这些问题。声明式服务基于组件模型理论,最早出现在R4 compendium规范之中,而Blueprint规范来源于Spring Dynamic Modules项目,最早出现于R4.2企业规范之中。
这两种方式的实现原理与适用场景均有所不同,最近来自Redhat的首席软件工程师Ioannis Canellos撰文对此进行了分析。
Blueprint是针对OSGi的依赖注入解决方案,用法非常类似Spring。当使用服务的时候,Blueprint会马上创建并注入一个代理(Proxy)。对这些服务进行调用时,如果服务在当前不可用的话,将会产生阻塞,直至能够获取到服务或超时。
声明式服务的处理方式有着较大的差异。声明式服务是一种组件模型,它简化了组件的创建过程,这些组件会发布和使用OSGi服务。Ioannis并没有将声明式服务视为依赖注入的解决方案,而是将其视为具备依赖管理功能的组件模型。我们需要以声明的方式定义组件及其依赖,框架会基于依赖的满足情况来管理组件的生命周期。这意味着,只有组件的依赖完全满足的时候,才会处于激活(activated)状态,一旦依赖出现了缺失,组件就会处于停用(deactivated)状态。因此,声明式服务没有使用代理,但是能保证只要组件处于激活的状态,它的内部依赖就是已满足的。从上面的介绍可以看出,两种方式的最大区别在于Blueprint采用了代理的方式,而声明式服务采用的是级联的方式(cascading),也就是激活或停用组件基于依赖是否能够满足。Ioannis更倾向于级联的方式,因为代理的方式无法保证底层对象的状态以及可用性。级联的方式能够更好地处理OSGi框架的动态化特性。
在使用代理方式时,如果服务对象在运行期不存在了,将会导致错误。另外一个问题在于即便服务的依赖还没有得到满足,也是可以发布服务的。而调用时,将会导致挂起,代理会等待未满足的依赖,这个过程会一直持续,直到依赖满足或超时为止。
Ioannis在文章中还举了一个现实中的例子来阐述这一过程。如下图:
此时应用由四部分组成,即展现层、Item Service、DataStore以及数据库。在OSGi中展现层可以使用基于HttpService注册的servlet,Item Service为封装了逻辑的OSGi服务,而DataStore是用来与数据库交互的OSGi服务。Web应用依赖于Item Service,而Item Service又依赖于DataStore。
当DataStore没有配置或不可用时,代理方式和级联方式分别会发生什么呢?在代理模式下,Item Service将会被注入DataStore的代理。即便没有可用的DataStore,Item Service也会被注册到服务注册中心,发送到Web应用的请求将会阻塞,等待可用的DataStore。而在声明式服务的级联场景下,情况会截然不同,Item Service只有在DataStore存在的时候才会注册为服务,同样,只有Item Service可用时,Web应用才会处于可用的状态。所以我们能够保证当Web应用可用的时候,它的依赖层级都是满足的。当DataStore可用的时候,Item Service和Web应用会自动探测到这种变化,并使自身处于可用的状态。
总之,声明式服务是很强大的依赖管理工具,级联的方式对于构建健壮的动态化、模块化应用是很有价值的;而Blueprint简单易用,尤其是对于熟悉Spring的开发人员来说更是如此。Ioannis认为当构建的组件没有服务依赖时或不会将自身导出为服务时,Blueprint方式很适合;而在其他的情况下,“等待服务”的方案更为合适,如shell命令或camel routes,因为在这里会有很长的依赖链,组件又是高度动态化的,声明式服务更好一些。
在OSGi的官方站点的介绍中,声明式服务、Blueprint以及Apache iPOJO,均被归类为组件模型。按照《OSGi实战》一书的作者们看来,这两种组件模型的适用场景可以归结为:
- 声明式服务主要用于创建可快速启动的轻量级组件;
- Blueprint主要用于创建高度可配置的企业级应用。
Blueprint阻塞机制的一个好处在于能够应对在bundle更新期间服务取消和发布对框架的影响。除此之外,因为Blueprint方式使用了代理机制,因此服务必须要以接口的方式发布。
除了官方的两种组件模型外,Apache iPOJO也是OSGi中常见的组件模型。它的实现机制与上面的两种方式又有所不同,iPOJO也是基于代理的机制,但是会使用字节码生成机制,而不是Java的动态代理机制,这样的话,就解除了服务必须要实现接口的限制。另一方面,当服务不可用时,调用线程不会阻塞,而是会使用null对象来进行处理,这个null对象基于模拟对象模式创建,所有的方法不执行任何操作,根据方法的返回类型生成默认的返回值,如null、0或者false。除此之外,iPOJO还支持提供默认实现。
根据我们上面的分析,可以看出每种方式都有其优势和适用场景,我们在使用的时候,有必要对内部原理有一定的了解,只有这样,当遇到相关的问题时,才能快速地进行分析和定位。
我们一直都在努力坚持原创.......请不要一声不吭,就悄悄拿走。
我原创,你原创,我们的内容世界才会更加精彩!
【所有原创内容版权均属TechTarget,欢迎大家转发分享。但未经授权,严禁任何媒体(平面媒体、网络媒体、自媒体等)以及微信公众号复制、转载、摘编或以其他方式进行使用。】
微信公众号
TechTarget
官方微博
TechTarget中国
相关推荐
-
OSGi中的服务模型与扩展者模型
在OSGi中,实现bundle间交互和扩展性有两种常见的方式,也就是服务模型(service model)和扩展者模型(extender model)。
-
模块化和OSGi引领Liferay开源门户未来
IT解决方案公司怎样采用开源WEB门户平台,并把它转变为市场上可销售的产品?开源门户网站的未来又是怎样的?
-
Java百万富翁的诞生:Liferay和OSGi市场帮大忙?
智能手机的兴起,使用移动应用成为市场主流,成为一种时尚。也使一部移动应用开发者一夜致富。那么对于Java开发都有没有可能会一夜致富呢?
-
企业OSGi应用开发教程
在JavaOne 2011上,Peter Kriens关于OSGi做了两个介绍。Kriens的演讲解释了为什么尽管OSGi表现的很难,用OSGi实现模块化对于今天的应用开发者来说是很有价值的。他也解释了如何进入这个领域,同时澄清了一些关于OSGi和模块化应用的错误概念。那么对于模块化应用开发的未来是怎么样的?企业中OSGi应用开发如何实现?在这本教程中我们将为您详细介绍。