技术开发 频道

OSGi Java模块化框架的另类进化

  导入和导出的粒度(granularity)

  从模块导入和导出内容的粒度应该是怎样的?由于存在各种嵌入等级,Java 中有多种等级的粒度。方法和域嵌入到类中,类又嵌入到包中,包嵌入到模块或 JAR 文件中。

  不难看出共享等级不应是方法和域。导入一个类的某些方法而排除例外一些,这种方式很明显是荒唐的。不仅仅这种方式是如此。我们可以为某个模块中类写一些方法/域,在另一个模块中再写一些方法/域,这种方式也同样是不可行的。想象一下,为在模块中的每一个共享方法写一些导入和导出列表,运行时对这些列表进行检查以及诊断为题的复杂度将是非常恐怖的,会出现许多错误,因为类并不是设计用来在运行时进行分割的。

  现在看看另一个极端,共享等级也不应是整个模块,因为这样模块就不能隐藏实施细节的部分,导入方将经常性地遇到“买下整个商店”的问题。

  所以唯一合理的选择是类和包。老实说,选择类也不是那么合理。虽然没有方法/域那么糟糕,但类的数量非常多,由于它太过于依赖同一个包中的其他类,无论是将类列出作为我们的导入和导出,还是将包中的一些类划分到某个模块同时将同一个包中另一些类划分到了另一个模块中,都是不合理的。

  最终的结果,OSGi 选择了包。Java 包的内容通常具有某种程度的一致性,但列出导入和导出的包并不是那么麻烦,而且在某个模块加入一些包而在另一个模块在加入另一些包,并不会对如何东西造成损坏。应该属于模块内部的代码可以放到一个或多个非导出的包中。

  我们的损失的无法干净地处理那些所谓的“分裂包”(split-package)。在 OSGi 中,包是进行共享的最基本单元:当导入个包时,你获得一个模块导出包的所有内容而不包括其他内容。一些传统的包,一直坚持在许多模块中共享包内容,对于这些包也存在一些方法进行处理,但这好过对每个包进行调整以便让它作为整体只能由某个模块导出。

  包连线(wiring)

  既然对于模块如何自我分离然后再连接有了一个模型,我们现在可以想象创建一个框架,这个框架将为这些模块构造实际的运行时实例。它将负责安装模块以及构造类加载器(这些类加载器知道相应模块的内容)。

  然后它将查看新安装的模块的导入,并试图找到匹配的导出。假设模块 A 导出包 com.foo,模块 B 要导入这个包。该框架将通知 B,它可以从模块 A 获得 com.foo 的类,这个称为连线(wiring)。如果 B 的类加载器要加载类 com.foo.Bar,它将委派 A 的类加载器来做。对整个模块的导入进行连线的过程成为解析(resolution),当所有导入都成功进行连线后,那么这个组件(bundle)就被解析(resolved)了,这将令它完全可用。

  一个预料之外的好处是我们可以动态地安装、更新和卸载模块。对于已经解析的模块,安装新模块对它们没有影响,虽然这可能导致某些之前不可解析的模块变得可解析。当进行卸载或更新时,该框架非常清楚那些模块受到影响,并且如果需要它将更改它们的状态。为了能够顺利地进行,还有一些额外的细节需要处理,比如,一个模块在卸载或者取消解析之前正在做非常的事情,那么需要向它发送通知,以便让它干净利落地关闭。所以,OSGi 中的动态模块并不是凭空出现的,这里并没有什么神奇的功能,但 OSGi 至少让它成为可能。

  某些 OSGi 用户更喜欢避免动态加载,这样做没有问题。这不是 OSGi 最重要的功能,但由于对于 OSGi 它是少有的,英尺获得了过多的关注。无论如何,没有人强迫你使用它,即使从来不去利用动态性的优势,你仍然能够从 OSGi 获得许多好处。

0
相关文章