使用jlink工具模块化Java应用,结合Docker优化容器镜像(上)

如果你曾经尝试使用Java的模块,你可能已经发现模块化并不容易。第一个障碍可能就是将你自己的应用模块化,但更多的问题来自于第三方库模块化的现行状态。这是不幸的,因为一个应用被模块化一次,就可以当作一个可执行文件被分布到经过裁剪的JDK版本上运行。于现在的容器化时代来说,这就是一个小型的Docker镜像。

通过这篇文章,我会解释如何使用jlink。这是一个从Java9就可用的命令行工具,可以用来创建一个可执行的Java容器。我将通过对模型的概述快速开启,然后展示如何使用jlink创建可执行文件,最后讲述如何使jlink与Docker容器完美的融合。

这篇文章完整的源码和文件都提交在GitHub,文中更大的项目源码和配置文件也可以在GitHub上找到。

jlink和模块化

一个类A在编译或者运行的时候可能会使用其它的类,比如java.util.List。

存在一个问题,就是JRE包含了很多的类文件,其中很多都不会被程序所使用,但它们总是会被捆绑到程序中。举个例子,一个运行中的服务型应用仍然绑定了如javax.swing的图形包。

另一个问题是,Java应用依赖于JRE是如何管理类可见性的。为了使ch.frankel.a中的类A对ch.frankel.b中的类B是可见的,class A就必须具有公共的可见性。记住,第三方的JAR库是无法将他们的API类文件和内部类文件从不同的包中彻底的分离。从历史的观点来讲,包就是内部依赖的隐式命名,比如ch.frankel.c.internal。但是,之前是没有如何技术手段强制执行这个约束的。

Java9尝试通过提供另一种方法(模块)管理包的可见性,从而解决这个问题。以下是几种模块:

  • 系统模块:这个模块通过JVM提供。
  • 应用模块:一个应用通过提供一个它根目录下的module-info文件使之变为一个应用模块。
  • 自动模块:在JAR的MANIFEST.MF中添加一个Automatic-Module-Name的实体,这个指定的模块将会被处理为一个自动模块。
  • 无命名模块:除了系统、应用、自动模块之外的JAR。

一个Java9(或之后)的应用是使用位于根目录下的一个module-info文件进行模块化得来的。这是一个表单文件,包含了模块的名字和声明所有需要的依赖模块。运行应用的时候,加载器读取表单文件,只加载所有必要的模块。

通过这个设计,使得可以排除JDK中不必要的部分模块,这也是jlink的任务。正如官方文档所描述的,“你可以通过jlink工具对一组模块和它们的依赖组装和优化进一个定制化的运行时镜像。”

jlink使你能够使用应用下的模块配置实现一个定制化的JRE服务于应用。使用同一个机器,jlink允许你无需多余的操作就能创建一个可执行文件,所以可交付的应用完全自给自足,并且不依赖目标系统是否拥有一个兼容的JRE环境。

Docker基础

让我们使用最简单的应用:Hello World,来开始学习jlink,这是它无比荣耀的地方:

// Main.java
public class Main{
	public static void main(String[] args){
		System.out.println(“Hello world”);
	}
}

不可否认的是,Docker是当今最受欢迎的容器分配渠道。使用Docker对这个“Hello World”程序进行分配是相当受益的。因为我的意图是创建一个独立的Dockerfile并使得最终的镜像尽可能地小,所以需要对它进行多级构建。

提醒一下,Docker的多级地构建允许你在之后的构建阶段复用之前阶段构建的结果形成链式构建。总之,每一个阶段可以继承不同的基础镜像,并且你可以对每个阶段进行命名。因为这会使得通过名字比使用索引对每个阶段进行引用时更容易。最大的好处是,多级构建可以在每个阶段中尽可能地使用相关的镜像,所以在构建过程的最后得到一个作为结果的最小镜像。

这是一个示例,用于展示Dockerfile是如何使用Maven为Hello World创建一个镜像的。假设项目架构兼容Maven:

Dockerfile
FROM maven:3.6-jdk-12-alpine as build

WORKDIR /app

COPY pom.xml
COPY src src

RUN mvn package

FROM openjdk:12-alpine

COPY --from=build /app/target/jlink-ground-up-1.0-SNAPSHOT.jar \
		  /app/target/jlink-ground-up.jar

ENTRYPOINT [“java”,”-jar”]
CMD [“/app/target/jlink-ground-up.jar”]

在这个文件中,第二行标志着多级构建中的第一个阶段,就是使用Maven的镜像并标记为构建(build)。然后,使用mvn package命令生成JAR,并使用默认命名,即jlink-ground-up-1.0-SNAPSHOT.jar。

接下来以FROM开头的一行,即为构建过程的第二阶段也是最后一个阶段。这一行会尽可能地在Linux发行的Alpine地JDK镜像中选择一个最小的镜像。Alpine镜像不提供Java11的版本,但有一个是支持Java12的。接下来的COPY命令则声明接下来使用的JAR文件是第一个阶段(被命名为build)的输出文件。接下来你就可以创建自己的镜像了:

$ docker build –t jlink:1.0

最主要的问题是,这个方法生成的Docker镜像是很大的,因为它嵌入了整个JDK,然而这只是个Hello World程序。事实上,这个程序本身和OpenJDK12相比是微不足道的:

$ docker images

 

因为没有优化,所以Docker生成的Hello World镜像与JDK地大小基本一致。为了减小一个镜像的尺寸,你可以利用系统模块以及只分配JDK中被使用的模块。(之后的文章会介绍)

关于jlink的入门基础知识的讲解就是这些了,在之后的文章中,我们将系统的解决以上出现的问题。比如,如何构建一个定制化的启动器、如何将jlink运用于开发产品中、如何添加模块依赖和非模块依赖等等。

使用jlink工具模块化Java应用,结合Docker优化容器镜像(中)

使用jlink工具模块化Java应用,结合Docker优化容器镜像(下)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章