java9模块化

定义java9模块

模块是代码、数据和资源的集合。它是一组相关包和类型(类、抽象类、接口等),包含代码、数据文件和一些静态资源。

例如,模块描述符module-info.java是Java9模块中的资源之一。

每个模块质保函一组相关代码和数据,以支持单一责任原则(SRP):一个类改变的原因不应超过一个。简单的说法是模块=代码+数据

基本模块和可靠配置

目前java9有98个模块,但它仍在继续发展。Oracle将jdk jar和java se规范分为俩组模块。

所有JDK和用户定义模块的默认模块都是基本模块java.base文件。它是一个独立的模块,不依赖于任何其他模块。java.base文件被称为java9模块之母

该图是一个依赖关系图。每个框表示一个模块,如果一个模块有一个指向另一个模块的箭头,则表示该箭头所指向的模块需要它所指向的模块来执行其功能。

正如你在图片中看到的, java.base 文件是其他模块所依赖的模块。这是因为它包含Java对象、整数、字符串等基本类。没有这些类,代码就不能工作。

我可以使依赖关系更加明确:

java8时的模块

rt.jar

rt.jar代表runtime JAR,并且包含在引导类(bootstrap classes0)来自Core API Java的所有类。

Windows和Linux中,rt.jar位于JRE的lib目录下。JDK1.7之前MacOSX中叫classes.jar。从java7开始也该位rt.jar。很多开发者尝试把自己的类放进rt.jar中,但最好不要这样做,因为rt.jar包含了JVM新人的class文件,jvm加载时不会对其他class文件进行严格的安全检查。

  1. rt.jar代表runtime,包含所有核心java运行环境的已编译class文件
  2. 必须在类路径中包含rt.jar,否则您无权访问核心类,例如 java.lang.String,java.lang.Thread,java.util.ArrayList或java.io.InputStream等,以及所有Java API中的其他类。 可以使用IDE打开并查看rt.jar中的内容, 它不仅包含所有Java API,还包含com包中指定的内部类。
  3. 在windows中,rt.jar位于$ JAVA_HOME / jre / lib下。 即使没有安装JDK并且只安装JRE,也会在完全相同的位置看到它,在$ JAVA_HOME / lib目录中找不到rt.jar。 顺便提一句,在MacOSX上,它被称为classes.jarand,位于/ System / Library / Frameworks // Classes目录下。
  4. 关于rt.jar一个很重要的事情就是,JVM知道这个jar文件中的所有类,这意味着JVM在任何位置加载都不会执行检查。之所以这样做是由于各种性能原因,这就是为什么这些class由bootstrap或primodial类加载器加载的原因。所以最好不要尝试将自己类文件包含在rt.jar中,java官方也不建议这样做。

核心模块

需要知道,核心jar包通常包含了Java平台的基础类库,如java.lang、java.util、java.io等等。这些类库提供了Java程序运行所需要的核心功能。

非核心jar包则是指那些不是Java应用程序或框架必需的 jar包,这些jar包提供了一些额外的功能和工具类库,比如数据库驱动、日志管理、ORM框架等等。它们通常是可选的,可以根据需要引入。

在Java应用程序或框架中,通常会使用一些第三方依赖库,这些依赖库会以jar包的形式提供。这些jar包可以是核心的,也可以是非核心的。在应用程序或框架的构建和部署过程中,需要将这些jar包一并打包,以便于程序的使用和运行。

在安装java包时,会遇到两次路径选择,第一次时选择jdk的路径,第二次是选择JRE的路径,如果所选择的jdk安装路径和jre的安装路径相同,那么jre包中的内容会覆盖掉jdk中的内容,因此,在你安装完成之后,会发现找不到tools.jar和dt.jar包

模块化简介

什么是模块系统,官方的定义是A uniquely named, reusable group of related packages, as well as resources (such as images and XML files) and a module descriptor.(一组唯一命名、可重复使用的相关包,以及资源(如图像和XML文件)和模块描述符。)

如图模块的载体是jar文件,一个模块就是一个jar文件,但相比于传统的jar文件,模块的根目录下多了一个module-info.class文件,也即(module descriptor)包含以下信息

  • 模块名称
  • 依赖哪些模块
  • 导出模块内的哪些包(允许直接import使用)
  • 开放模块内的哪些包(允许通过java反射访问)
  • 提供哪些服务
  • 依赖哪些服务

也就是说任意一个jar文件,只要加上一个合法的module descriptor,就可以升级为一个模块。这个看似微小的改变,可以带来哪些好处?

  1. 原生的依赖管理。有了模块系统,java可以依据module descriptor计算出各个模块之间的依赖关系,一旦发现循环依赖,启动就会终止。同时模块系统不允许模块导出相同的包(即split package分裂包),所以在查找包时,java可以精准定位到一个模块,从而获得更好的性能。
  2. 精简 JRE。引入模块系统之后,JDK 自身被划分为 94 个模块(参见图-2)。通过 Java 9 新增的 jlink 工具,开发者可以根据实际应用场景随意组合这些模块,去除不需要的模块,生成自定义 JRE,从而有效缩小 JRE 大小。得益于此,JRE 11 的大小仅为 JRE 8 的 53%,从 218.4 MB缩减为 116.3 MB,JRE 中广为诟病的巨型 jar 文件 rt.jar 也被移除。更小的 JRE 意味着更少的内存占用,这让 Java 对嵌入式应用开发变得更友好。
  3. 更好的兼容性。自打 Java 出生以来,就只有 4 种包可见性,这让 Java 对面向对象的三大特征之一封装的支持大打折扣,类库维护者对此叫苦不迭,只能一遍又一遍的通过各种文档或者奇怪的命名来强调这些或者那些类仅供内部使用,擅自使用后果自负云云。Java 9 之后,利用 module descriptor 中的 exports 关键词,模块维护者就精准控制哪些类可以对外开放使用,哪些类只能内部使用,换句话说就是不再依赖文档,而是由编译器来保证。类可见性的细化,除了带来更好的兼容性,也带来了更好的安全性。
  4. 提升 Java 语言开发效率。Java 9 之后,Java 像开挂了一般,一改原先一延再延的风格,严格遵循每半年一个大版本的发布策略,从 2017 年 9 月到 2020 年 3 月,从 Java 9 到 Java 14,三年时间相继发布了 6 个版本,无一延期,参见图-4。这无疑跟模块系统的引入有莫大关系。前文提到,Java 9 之后,JDK 被拆分为 94 个模块,每个模块有清晰的边界(module descriptor)和独立的单元测试,对于每个 Java 语言的开发者而言,每个人只需要关注其所负责的模块,开发效率因此大幅提升。这其中的差别,就好比单体应用架构升级到微服务架构一般,版本迭代速度不快也难。

模块化将rt.jar做了拆分,导致了ClassLoader的响应调整

java9之前的ClassLoader

  • 启动类加载器(Bootstrap):也叫引导类加载器,C++实现,在jav中无法获取,负责加载JAVA_HOME/lib下的类
  • 拓展类加载器(Ext):JAVA实现,在java里获取,负责加载JAVA_HOME/lib/ext下的类
  • 应用程序类加载器(Application):也称为系统类加载器,一般情况下这个就是程序中默认使用的类加载器,我们写的代码默认就是由他来进行加载

java9之后的classloader

  • bootstrap:加载lib/modules
  • Ext:更名为Platform ClassLoader(平台类加载器)加载lib、modules
  • Application:加载-cp,-mp指定的类,父类不再是URLClassLoader

定制JRE

导出可执行 JAR 文件

用 Eclipse 打开项目,选择 File - Export - Java - Runnable Jar File。选择要导出的项目,在 Library Handling 一栏,选择“Copy all required lib next to the generated jar”,点击 Finish。

注意:设定导出位置时,jar 文件的文件名时不能含空格。

假设我们导出 MyApp.jar,在导出目录下会有 MyApp.jar 本身以及 MyApp_lib 文件夹。后者存放了 MyApp.jar 依赖的 JAR 文件。把导出目录作为程序目录,把其他的资源文件也复制进来。

定制 JRE

转到 JDK 的 bin 目录下,使用 jdeps 命令查看所使用的 jar 包(包括 MyApp_lib 下的 JAR 文件)依赖的模块。假设 MyApp.jar 被导出到了 D:MyApp 下,则查看 MyApp.jar 的依赖的方式为:

jdeps "D:\MyApp\MyApp.jar"

知道需要的依赖后,使用 jlink 导出你的定制 JRE。jlink 命令的格式为:

jlink --output <定制 JRE 的导出路径> --module-path <jmods 文件夹的路径> --add-modules <依赖的模块>

假设 jdeps 提示需要的依赖有 java.base、javafx.base、javafx.graphics、javafx.controls,把 jre 导出到 D:MyAppjre 下,则使用命令:

jlink --output "D:\MyApp\jre" --module-path ..\jmods --add-modules java.base,javafx.base,javafx.graphics,javafx.controls

顺利的话,D:MyApp 下会出现 jre 文件夹,这个文件夹就是我们需要的定制 JRE。

注意:jlink 和 jdeps 是 JAVA 9 开始才有的东西。

制作 .exe 文件

在程序目录下新建一个 .bat 文件,在其中键入命令,使其调用我们定制的 JRE 中的 javaw.exe 运行 jar 文件。例如:

start jre/bin/javaw -jar MyApp.jar

双击运行,应该可以看到 JAVA 程序启动。

最后使用 bat to exe converter 将 bat 文件转为 exe。在 Options 中可以做一些设置,比如设置程序图标,是否要显示控制台等。把 exe 文件放在程序目录下,双击它,就可以运行我们的 JAVA 程序。

Last modification:December 20, 2023
如果觉得我的文章对你有用,请随意赞赏