Java 9,OSGi以及模块化的未来

  JPMS方法的缺点是,它不可能有重叠内容的模块。也就是说,如果两个模块都包含一个私有(非导出)的包org.example.util,这些模块不能同时在模块路径上被加载——它会导致layerinstantiationexception异常。通过应用程序实例化类加载器可能会解决此限制——但这正是OSGi已经为我们做的!

  再次强调,完全是通过设计允许JPMS模块化JDK的内部。但结果是,你会有不能完全一起工作的模块,因为它们内部的实现细节有冲突。

  复杂性

  对于OSGi最常见的抱怨之一是,它给开发人员增加了复杂性。这有一定的道理,但是有这些抱怨的人都搞错了复杂性的原因。

  模块化并不是一个在应用程序发布前洒在上面的神奇的尘埃。它是在设计和开发各个阶段必须遵循的准则。一些开发人员已经意识到了OSGi带来的巨大收益,他们在早期就开始使用OSGi并且在编写一行代码之前会运用模块化思想,他们发现OSGi实际上是非常简单的,尤其是在使用现代化OSGi工具链时,它自动生成元数据并且在运行前做了大量的一致性检查捕获异常。

  而另一方面,开发人员试图把OSGi引入现有的大型代码库时遭遇了困难,因为这些代码很少能够模块化以便迁移。没有执行模块化的准则,很容易走捷径,打破封装性。BEA WebLogic的一个开发人员告诉我,在Oracle收购BEA之前:“我们以为我们是模块化的,直到我们开始使用OSGi。”

  除了非模块化的应用程序,OSGi的采用也受到非模块化库的阻碍。一些流行的Java库中类加载和全局可见性的假设在模块化结构中被打破了。OSGi做了大量工作,让它可以使用这些库,这是OSGi规范明显复杂性的来源。我们需要有一定的复杂性来处理混乱的、复杂的现实世界。

  我们很快就会看到,JPMS也会有同样的问题——可能更是如此。如果你的组织曾试图采用OSGi,却因为迁移工作量过大而放弃了,那么当你要迁移到JPMS时,至少应该预期会有同样多的工作量。只需要看看Oracle在模块化JDK时的经验:有很多的工作要做,导致Jigsaw从Java 7延迟到Java 8,再到Java 9,甚至Java 9已经延迟了一年(到目前为止)。

  Jigsaw项目开始于一个目标就是越来越简单,但JPMS规范大大增加了复杂性:与类装载器模块的相互作用;分层结构和配置;re-exporting要求;弱模块;静态要求;qualified导出;dynamic导出;跨层继承的可读性;多模块JAR文件;自动模块;未命名的模块等等,已经非常清晰所有的这些功能都会作为需求添加进来。类似的过程也发生在OSGi,只是它有16年领先的优势。

  依赖:包vs全部模块

  隔离只是模块化的一个难题:模块仍然需要协同工作和通信。模块之间建立“墙”后,它们需要以一个可控的方式重新连接。一个模块化系统必须定义模块访问其他模块功能的方式。可以通过在类型级别上静态地或者动态地使用对象来实现。

  静态依赖在编译时就是已知的和可控的。如果一个类型在一个模块的边界引用另一个类型,那么模块系统需要提供一个方法让该类型可见并且可访问。有两种方式:模块需要有选择性地暴露一些内部类型,模块需要指定自己使用了其他模块的哪些类型。

  导出(Exports)

  在OSGi和JPMS中,类型暴露在Java包级别就完成了。在OSGi使用Export-Package语句声明指定名称的包对其他bundle是可见的。它看起来像这样:

  Export-Package: org.example.foo; version=1.0.1,

  org.example.bar; version=2.1.0

  该声明在META-INF/ MANIFEST.MF文件中。OSGi初期大多数开发人员会手工指定这样的声明;但我们越来越倾向于使用构建工具生成。现在最流行的方式是在Java源代码中添加注解,Java 5中引入了package-info.java文件允许包级别的注解和文档,所以OSGi中可以如下编写:

  @org.osgi.annotation.versioning.Version("1.0.1")

  package org.example.foo;

  这是一个有用的模式,因为想要导出一个包时可以直接在该包中表示。版本也可以在这里显示,包的内容变化时在附近就可以更新 2 。

  JPMS中包的导出在module-info.java文件中,如下:

  module A {

  exports org.example.foo;

  exports org.example.bar;

  }

  请注意,如果缺少version,JPMS中模块和包都不能被版本化;稍后我们会讨论这一点。

  Imports/Requires

  虽然在导出时OSGi和JPMS是类似的,但是导入或对其他模块的依赖却有显著的差异。