Java 9模块化

前言

JPMS(Java Platform Module System) Java平台模块系统是Java 9的主要增强。它也被称为Jigsaw项目。在本文中,我们将简单学习模块,以及在将来开始编写模块化代码时,编程风格将如何变化。

正文

什么是模块?

在任何编程语言中,模块(类似于包)都是包含代码的构件,其中包含描述模块及其与其他模块的关系的元数据。理想情况下,这些构件从编译时一直到运行时都是可识别的。任何应用程序通常都是多个模块的组合,这些模块一起工作以执行业务目标。

在应用程序架构方面,模块应该表示特定的业务功能。对于该功能,它应该是自给自足的,并且应该只公开使用模块功能的接口。为了完成它的任务,它可能依赖于其他模块,它应该显式地声明这些模块。

因此,简而言之,一个模块应该遵循三个核心原则:

  • 强大的封装(Strong Encapsulation)

    封装意味着隐藏实现细节,这些细节对于正确使用模块并不重要。其目的是封装的代码可以自由改变而不影响模块的用户。

  • 稳定的抽象(Stable Abstraction)

    抽象有助于使用接口(即公共api)公开模块功能。任何时候,如果想要更改模块代码中的业务逻辑或实现,更改对模块用户都是透明的。

  • 显式的依赖关系(Explicit dependencies)

    模块也可以依赖于其他模块。这些外部依赖必须是模块定义本身的一部分。模块之间的这些依赖关系通常用图表示。一旦您看到应用程序级别的图,您将更好地理解应用程序的体系结构。

Java 9模块化介绍

在Java 9之前,我们有“包(packages)”来根据业务功能对相关类进行分组。除了包之外,还有“访问修饰符”来控制哪些是可见的,哪些是隐藏在其他类或包中的。到目前为止,它运行得很好。Java对封装和抽象提供了强大的支持。

但是,显式依赖关系是事情开始崩溃的地方。在java中,依赖项是用“import”语句声明的;但是它们是严格的“编译时”构造。一旦代码被编译,就没有明确的机制来声明它的运行时依赖关系。事实上,java运行时依赖项解析是一个非常有问题的领域,因此专门创建了一些工具来解决这个问题,例如gradle或maven。此外,很少有框架捆绑它们的完整运行时依赖项,例如Spring boot项目。

有了新的Java 9模块,我们将能够更好地编写结构良好的应用程序。这种增强分为两个方面:

  1. 模块化JDK本身。
  2. 提供一个模块系统供其他应用程序使用。

Java 9模块系统有一个“java.base”模块。它被称为基模块。它是一个独立的模块,不依赖于任何其他模块。默认情况下,所有其他模块都依赖于“java.base”。

在Java 9中,模块帮助我们封装包并管理依赖项。所以通常情况下,

  • 类是字段和方法的容器
  • 包是类和接口的容器
  • 模块是包的容器

如果我们不知道要查找的具体内容,那么我们不会感觉到普通代码和模块化代码之间的任何主要区别。如:

  • 模块通常只是一个jar文件,在根目录下有一个module-info.class文件。
  • 要使用模块,请将jar文件包含到modulepath中,而不是classpath中。添加到classpath中的模块jar文件是普通的jar文件,而module-info.class文件将被忽略。

如何编写模块化代码

在阅读了所有上述概念之后,让我们看看模块化代码是如何在现实中编写的。我使用Netbeans IDE是因为它对Java 9有很好的早期支持(到今天为止)。

创建Java模块项目

创建一个Java模块项目,命名为JavaAppOne

upload successful

upload successful

创建Java模块

我们向这个项目中添加两个模块,如下:

upload successful

我向项目中添加了helloworldtest 模块,它们的结构如下:

upload successful

相关代码如下:

/helloworld/module-info.java

1
2
module helloworld {
}

HelloWorldApp.java

1
2
3
4
5
6
7
package com.howtodoinjava.demo;

public class HelloWorldApp {
public static void sayHello() {
System.out.println("Hello from HelloWorldApp");
}
}

/test/module-info.java

1
2
module test {
}

TestApp.java

1
2
3
4
5
6
7
package com.test;

public class TestApp {
public static void main(String[] args) {
//some code
}
}

到目前为止,模块是独立的。现在假设,我们想在TestApp类中使用HelloWorldApp.sayHello()方法。如果我们尝试在不导入模块的情况下使用该类,我们将得到编译时错误“package com.howtodoinjava.demo is not visible”。

导入模块信息

为了能够导入HelloWorldApp,我们必须首先从helloworld模块导出“com.howtodoinjava.demo”包,然后在test模块中包含helloworld模块。

1
2
3
4
5
6
7
module helloworld {
exports com.howtodoinjava.demo;
}

module test {
requires helloworld;
}

在上面的代码中,require关键字表示依赖项,exports关键字标识可以导出到其他模块的包。只有当一个包被显式导出时,才能从其他模块访问它。模块内未导出的包在默认情况下无法从其他模块访问。

现在我们可以在TestApp类中使用HelloWorldApp类了。

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.test;

import com.howtodoinjava.demo.HelloWorldApp;

public class TestApp {
public static void main(String[] args) {
HelloWorldApp.sayHello();
}
}

Output:

Hello from HelloWorldApp

模块关系图如下:

upload successful

从Java 9开始,public意味着只对该模块内的所有其他包公开。只有在导出包含公共类型的包时,其他模块才能使用它。

结语

模块化应用程序有许多优点,当我们遇到具有非模块化代码库的应用程序时,我们会更加欣赏这些优点。模块化并不是什么灵丹妙药,但它是一种体系结构原则,如果应用得当,可以在很大程度上避免项目依赖混乱问题。

有了JPMS, Java向成为模块化语言迈出了一大步。这个决定是对是错,只有时间能证明。第三方库和框架如何适应和使用模块系统将会很有趣。以及它将如何影响开发工作,由我们来见证。




-------------文章结束啦 ~\(≧▽≦)/~ 感谢您的阅读-------------

您的支持就是我创作的动力!

欢迎关注我的其它发布渠道