探索 Java 的未来





4.00/5 (1投票)
探索 Java 的未来
Java 6 于 2006 年发布,Java 7 于 2011 年发布。Java 7 中没有大的改动,许多新功能都包含在 Java 6 的更新中。Java 7 之后,Oracle 决定实施为期两年的 Java 规划路线图,下一个主要版本将于 2013 年发布。
Java 8 包含一些有趣的新功能,例如 lambda 表达式,但计划中的主要新功能是 Jigsaw。事实上,Jigsaw 最初是为 Java 7 设计的,但被推迟到 Java 8,现在又移到了 Java 9。
这里是对 Jigsaw 网站上关于 Jigsaw 动机的简要描述:
“Jigsaw 的最初目标是设计和实现一个模块化系统,该系统严格专注于模块化 JDK 的目标,并将其应用于 JDK 本身。我们期望由此产生的模块化系统对开发人员使用自己的代码很有用,并且将为此目的提供全面支持,但最初它并不打算成为 Java SE 平台规范的官方组成部分。”
在了解 Jigsaw 的目标时,我们可以问的第一个问题是:
为什么要发明另一个模块化系统?OSGi 方法不够吗?
如之前的帖子中所述,OSGi 是 Eclipse 使用的成熟方法,并且被许多其他项目使用。但 Jigsaw 的有趣之处在于它将被集成到 Java 平台中,并且如您稍后将看到的,JRE 本身就是用 Jigsaw 模块构建的,也许随着 Java 9 的到来,模块化方法将被更广泛地采用。
为了探索这项新功能,我们将分析实现 Jigsaw 的Java 8 自定义版。为此,我们将使用 JArchitect 来深入了解 Jigsaw 的设计和实现。
与之前的所有 Java 版本一样,标准 Java 8 是通过 jar 文件构建的,但使用 Jigsaw 后,结构发生了变化。这是标准 Java 8 的包依赖图。
JRE 是通过使用 jar 文件构建的,每个 jar 文件包含许多类型,并且几乎所有类型都包含在 rt.jar 中。这种结构不清晰,jar 文件之间存在一些循环依赖。然而,实现 Jigsaw 的 JRE 是通过使用模块构建的,每个主要功能都独立于特定模块,结构变得更清晰。
模块
模块是具有名称、可选版本号以及与其他模块关系形式化描述的 Java 类型集合。除了 Java 类型之外,模块还可以包含资源文件、配置文件、本地库和本地命令。模块可以进行加密签名,以便验证其真实性。
Java 编程语言已扩展到包含模块声明,用于定义模块、其内容以及与其他模块的关系。包含模块声明的编译单元,按照约定,存储在名为 module-info.java 的文件中,并编译为名为 module-info.class 的文件。
为了更好地理解模块概念,让我们深入了解 jndi 模块,这是它的声明:
module jdk.jndi @ 8-ea {
requires local jdk.auth.internal@8-ea;
requires local jdk.base.internal@8-ea;
requires optional jdk.desktop@8-ea;
requires jdk.rmi@8-ea;
requires jdk.tls.internal@8-ea;
requires optional service javax.naming.ldap.StartTlsResponse;
provides service sun.net.spi.nameservice.NameServiceDescriptor
with sun.net.spi.nameservice.dns.DNSNameServiceDescriptor;
// default view exports
exports com.sun.security.auth.*;
exports com.sun.security.auth.module.*;
exports javax.naming.*;
exports javax.naming.directory.*;
exports javax.naming.event.*;
exports javax.naming.ldap.*;
exports javax.naming.spi.*;
view jdk.jndi.internal {
exports com.sun.jndi.toolkit.url.*;
exports sun.net.dns.*;
permits jdk.cosnaming;
permits jdk.kerberos;
}
}
让我们来探索每个模块部分。
requires
Jndi 依赖于其他模块,到目前为止没有什么新鲜事,jar 文件也依赖于其他 jar 文件。但 Jigsaw 方法的新之处在于,我们指定了所有必需的模块及其版本,指定版本是正在使用的版本,我们还可以指定版本范围。
确实,Maven 也提供了一种指定 jar 之间依赖关系的有趣方式,但通过 Jigsaw,我们在定义模块依赖项时拥有更多的控制权和灵活性。
如果我们查找 jndi 使用的所有类型,它们必须存在于 requires
部分指定的模块中。
from t in Types where t.IsUsedBy (“jndi”)
select new { t, t.NbBCInstructions }
正如我们可以观察到的,所有使用的模块都已在模块规范中指定。
provides
服务是一组广为人知的接口和(通常是 abstract
)类。服务提供者是服务的特定实现。提供者中的类通常实现服务本身中定义的接口和类。
模块可以声明它提供服务,并使用“with
”关键字指定服务提供者。
例如,对于 jndi,在其模块声明中有以下一行:
provides service sun.net.spi.nameservice.NameServiceDescriptor
with sun.net.spi.nameservice.dns.DNSNameServiceDescriptor;
这规定它提供了 sun.net.spi.nameservice.NameServiceDescriptor 服务,其实现为 sun.net.spi.nameservice.dns.DNSNameServiceDescriptor
。
我们可以搜索 sun.net.spi.nameservice.NameServiceDescriptor
服务的**所有**服务提供者:
from t in Types where t.Implement (“sun.net.spi.nameservice.NameServiceDescriptor”)
select new { t, t.NbBCInstructions }
只有 jndi 中的 sun.net.spi.nameservice.dns.DNSNameServiceDescriptor
实现了该服务。
exports
模块声明中的 exports
子句使包中命名的 public
类型可供其他模块使用,因此我们可以使用 Jigsaw 定义边界,并且并非所有 public
类型都可以被其他模块使用,我们必须明确指定哪些类型是可见的。
jndi 模块导出了许多包,这意味着只有这些导出的类型才能被其他模块使用。
让我们查找 jndi 被其他模块使用的所有类型:
from t in Projects.WithNameIn( “jndi”).ChildTypes()
where t.NbTypesUsingMe>0 && t.TypesUsingMe.Where
(a=>a.ParentProject.Name!=”jndi”).Count()>0
select t
正如我们可以观察到的,一些包已经包含在 exports
子句中,但 com.sun.jndi.toolkit.url
包呢?它存在于 view
部分的 exports
子句中?
查看
在大型软件系统中,通常有用定义同一模块的多个视图。一个视图可以声明供任何其他模块通用,而另一个视图可以提供对仅供一组紧密相关的模块使用的内部接口的访问。
例如,对于 jndi,我们希望 com.sun.jndi.toolkit.url
仅对 cosnaming 和 kerberos 模块可见,如模块声明中所指定。
view jdk.jndi.internal {
exports com.sun.jndi.toolkit.url.*;
exports sun.net.dns.*;
permits jdk.cosnaming;
permits jdk.kerberos;
}
这样,我们在定义模块边界方面就有了更大的灵活性。
为了检查只有这两个模块能够访问 com.sun.jndi.toolkit.url
,让我们查找使用此包的所有类型。
from t in Types where t.IsUsing (“com.sun.jndi.toolkit.url”)
select new { t, t.NbBCInstructions }
正如我们可以观察到的,只有 cosnaming 模块使用了它,因为它有权限这样做。
通过 Jigsaw,一个模块可以被非常清晰地定义,并且这种新方法可能会消除 jar 之间的“依赖地狱”。
当然,我们可以使用工具检查依赖项,例如使用 CQLinq,我们可以添加规则来实现与 require
、exports
、provide
子句相同的功能,但最好将其集成到 JRE 中。
ModuleClassLoader
在标准方法中,我们有两个类加载器:
- VM 引导类加载器
- 系统类加载器
然而,在模块化方法中,我们有:
- VM 引导类加载器(稍后会讨论它的作用)
- 一个或多个模块的加载器
- 模块加载器用于启动应用程序。
- 模块加载器加载它们的依赖项,例如
java.base
,这些依赖项在加载类时被延迟创建。
当然,我们始终可以像 OSGi 那样定义自定义加载器,以对它们的 bundles 进行更多控制。
这是一个依赖图,显示了加载模块时涉及的一些类型。
结论
当我们发现模块化方法的重要性时,我们会想知道为什么它没有早些实现?为什么它每次都被推迟?为什么不使用成熟的 OSGi 而要创建新的替代方案?
也许在未来,我们会找到所有这些问题的答案,而 Jigsaw 将在 Java 世界中取得成功。