首页 / 软件开发 / JAVA / 模块化Java:声明式模块化
模块化Java:声明式模块化2010-11-29 infoq 译:宋玮前一篇文章,《模块化Java: 动态模块化》描述了如何通过使用服务 (service)给应用程序带来动态模块化特性。它们是通过输出的一个(或多个 )可以在运行时被动态发现的接口而实现的。尽管这种方式使得client和server 完全解耦,但是又带来一个如何(何时)启动服务的问题。启动顺序在彻头彻尾的动态系统里,服务不仅可以在系统运行的时候装卸,还可以以 不同的顺序启动。有时,这是个大问题:无论A和B的启动顺序如何,在系统达到 就绪状态并准备好接收事件之前,如果没有事件(或线程)出现,那么哪个服务 先启动都无大碍。可是,有很多情况都不符合这一简单假设。经典的例子就是logging: 通常, 服务在启动和做其他操作的时候,就要连接并开始写日志了。如果日志服务此时 还不可用,那会有什么后果?假定服务在运行时能够动态装卸,client应该能够应对服务不存在时的情况 。在这种情况下,它也许能聪明地转移到另一种机制(如输出到标准输出),或 者处于阻塞状态等待服务可用(对logging系统来说不是好的答案)。可是,让 服务启动之前就可用是不切实际的。启动级别OSGi提供了一种机制来控制bundle启动时的顺序,即使用启动级别(start levels)。这一概念是基于UNIX运行级别的概念:系统以级别1启动,然后单调 递增,直到达到目标启动级别。每个OSGi容器都提供了不同的默认目标级别: Equinox默认值是6;而Felix是1。启动级别可被用来创建bundle间的启动顺序,让关键bundle服务(比如 logging)的启动级别比那些需要用它的bundle更低。可是因为可 用的启动级别 值是有限的,而且安装程序倾向于选择单一数字作为启动级别,因此它并不能确 保你仅通过启动顺序就能解决问题。另一点值得注意的是,具有相同启动级别的bundle是各自独立启动的(可能 并行),因此,如果你有一个与log服务具有相同启动级别的bundle,谁也不能 保证log服务能够在需要的时候已经就绪。换句话说,启动级别可以解决大部分 问题,但不能解决所有问题。声明式服务解决这一问题的一个方案是OSGi的声明式服务(以下称为DS——declarative services)。用这一方法,各个组件是由外部bundle将他们组织在一起并决定他 们什么时候可用。声明式服务是通过在一个XML配置文件组织在一起的,文件中 描述了需要(消费)或提供什么服务。在上篇文章最后一个例子中,我们使用ServiceTracker去获得服务,如果必 要则需等待服务可用。如果我们把创建shorten命令延迟到shortening服务可用 之后会很有用。DS定义了一个组件(component)概念,其是比bundle更细粒度的概念,但是 比服务的概念粒度更大一些(因为一个组件可以消费/提供多个服务)。每个组 件都有一个名字,对应一个Java类,并可以通过调用该类的方法使其激活或失效 。与OSGi Java API不同,DS允许用纯Java POJO来开发组件,根本不需要从程序 上依赖OSGi。其附带的好处是让DS更加易于测试和模拟(test/mock)。为了说明这一方法,我们将继续使用前面的例子。我们需要两个组件:一个 是shortening服务本身,另一个是调用它的ShortenComand。第一项任务是用DS配置并注册shorten服务。我们可以让DS在服务启动时注册 它,而不是通过Bundle-Activator注册该服务。那么DS怎么知道要激活并连接谁呢?我们需要给Bundle的Manifest头增加一 个条目,其指示了一个(或多个)XML组件定义文件。Bundle-ManifestVersion: 2
...
Service-Component: OSGI-INF/shorten-tinyurl.xml [, ...]*
这个 OSGI-INF/shorten-tinyurl.xml组件定义文件内容如下:<?xml version="1.0" encoding="UTF-8"?>
<scr:component name="shorten-tinyurl" xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0">
<implementation class="com.infoq.shorten.tinyurl.TinyURL"/>
<service>
<provide interface="com.infoq.shorten.IShorten"/>
</service>
</scr:component>
当DS处理这一组件时,其效果与代码context.registerService( com.infoq.shorten.IShorten.class.getName(), new com.infoq.shorten.tinyurl.TinyURL(), null );基本一样。Trim()服务需要类 似的声明,在下面的源代码中包含着这部分内容。如果需要的话,一个单一组件可以基于不同接口提供多个服务。一个bundle 也可以包含多个组件,使用相同或不同的类,每个都提供不同的服务。