使用jlink工具模塊化Java應用,結合Docker優化容器鏡像(中)

在開始這篇文章的學習之前,希望您先學習前一篇的內容《使用jlink工具模塊化Java應用,結合Docker優化容器鏡像(上)》,對jlink和Docker有一個初步瞭解,會更有助於您理解下面的內容。如果您已經掌握了jlink和Docker的基礎內容,請直接往下閱讀學習。

構建一個自定義啓動程序

       Hello World是一個除了java.base之外不需要其它模塊的應用。像java.lang這樣的包都是被隱式導入應用的,所以這個模塊是被自動導入的。

       爲了構建一個更小的定製化可執行文件,第一步就是將應用移植到系統模塊中。如前面的描述,jlink可以單獨運行模塊化後的應用,因爲它依賴的是module-info文件。

       因爲這個應用只需要java.base模塊,所以直接這樣創建一個module-info.java描述這個模塊:

// module-info.java
module ch.frankel.jlink {
       exports ch.frankel.blog.jlink;
}

       jlink的特點就是優化一個應用,使之只保留將會被使用到的模塊。此外,它可以用優化後的應用版本創建一個獨立的可執行文件。因爲我們的應用現在利用的是系統模塊,所以它可以用來創建一個專用的啓動程序。但是jlink需要一個現有的JAR來發揮它的神奇作用:

$ mvn clean package

       現在所有東西都準備完成,是時候來使用jlink了。如瞭解java或javac命令一樣,jlink也需要指定的選項。以下是創建一個定製化的Hello World可執行文件的命令:

$ jlink --add-modules ch.frankel.blog.jlink \
	--module-path ${JAVA_HOME}/jmods:target/jlink-ground-up-1.1.jar \
	--output target/jlink-image \
	--launcher hello=ch.frankel.jlink/ch.frankel.blog.jlink.Main

接下來分析這些命令選項:

  • 第一行通過名字定義所需要的模塊,應用模塊的名字需要被設置。
  • 第二行指定模塊的路徑。Java9之前的應用使用classpath,而兼容的模塊使用的是模塊路徑。和classpath一樣,模塊路徑會參照路徑查找所有依賴的模塊。必須提及的是,現在每個模塊的路徑都包含了它們所提供的JDK和JAR文件。
  • 第三行指定輸出文件目錄。
  • 最後一行指定構建的入口點。它的格式包括幾個部分:最終可執行文件的名字(如hello)、模塊的名字(見module-info.java)、應用main函數的類路徑+類名。

創建完成之後,你可以通過這個命令啓動它:

$ target/jlink-image/bin/hello

執行這個命令會在控制檯打印“Hello world”。

跟之前的一樣,我們的目標是將這個定製化的模塊打包爲一個Docker鏡像。接下來做以下調整:

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

WORKDIR /app
COPY pom.xml
COPY src src
RUN mvn package && \
	jlink --add-modules ch.frankel.jlink \
	--module-path ${JAVA_HOME}/jmods:target/jlink-ground-up-1.1.jar \
	--output target/jlink-image \
	--launcher hello=ch.frankel.jlink/ch.frankel.blog.jlink.Main

FROM alpine:3.8

COPY --from=build /app/target/jlink-image /app
ENTRYPOINT [“/app/bin/hello”]

接下來的問題是這個特別的步驟是如何影響Docker鏡像的大小的。以下是與之前不用jlink創建鏡像的對比:

看,使用jlink創建的鏡像比之前沒有模塊化的應用節省了283MB的存儲容量!53MB看上去對Hello World應用來說還是太大了,但請記住,分配的過程中包含了JVM提供的所有功能,比如JIT的實時編譯器和垃圾回收管理。如果可以的話,先停下來享受當前的成果吧,當你準備好之後再進入下一節的學習。

自定義啓動程序的生成過程工業化

       之前的jlink命令比較冗長,此外,當依賴模塊的數量增加時會變得更加的冗長。這和Java編譯器的情況相似:日常生活中,開發者很少直接調用編譯器;而是更喜歡使用一個構建工具來替代,比如Maven。通過Maven,在POM文件中聲明所有的依賴,然後Java編譯器插件會幫你管理classpath。如果每次編譯時都手動管理classpath是十分尷尬的。

       好消息是,有一個現有Maven插件可以幫你聲明式地管理模塊路徑(module path):ModiTect[https://github.com/moditect/moditect]插件。我在接下來地文章中將使用這個插件。

       這個插件的其它功能是,提供一個create-runtime-image實現創建一個啓動程序地目的。這是一個POM文件地片段,如之前的描述創建一個自定義啓動程序,但是以一個可重複使用的方式:

<plugin>
	<groupId>org.moditect</groupId>
	<artifactId>moditect-maven-plugin</artifactId>
	<version>1.0.0.Beta2</version>
	<executions>
		<execution>
			<id>create-runtime-image</id>
			<phase>package</phase>	<!-- 標誌1 -->
			<goals>
				<goal>create-runtime-image</goal>  <!-- 標誌2 -->
			</goals>
			<configuration>
				<modulePath>  <!-- 標誌3 -->
					<path>  <!-- 標誌4 -->
						${project.build.directory}/${project.artifactId}-${project.version}.${project.packaging}
					</path>
				</modulePath>
				<modules>
					<module>ch.frankel.jlink</module>  <!-- 標誌3 -->
				</modules>
				<launcher>
					<name>hello</name>  <!-- 標誌3 -->
					<module>
						ch.frankel.jlink/ch.frankel.blog.jlink.Main
					</module>  <!-- 標誌3 -->
				</launcher>
				<outputDirectory>
					${project.build.directory}/jlink-image
				</outputDirectory>  <!-- 標誌3 -->
			</configuration>
		</execution>
	</executions>
</plugin>

標誌1的代碼行將目標執行生命週期綁定爲包階段;標誌2的代碼行聲明執行目標;標籤3的代碼行會被轉變爲jlink命令行的選項;標誌4的代碼行指定模塊信息的路徑。

這個表展示聲明的配置信息是如何對應jlink命令行選項的:

通過POM文件中添加的信息,可以對Dockerfile進一步的簡化:

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

WORKDIR /app
COPY pom.xml
COPY src src

FROM alpine:3.8

COPY --from=build /app/target/jlink-image /app
ENTRYPOINT [“/app/bin/hello”]

這時,你已經學會了製作一個可重複式創建自定義啓動程序的具體過程。在下一篇文章中我們將介紹:項目依賴的是模塊和非模塊的處理方式。相信目前通過兩篇文章的講解,你基本已經對jlink的使用已經爐火純青了,並且也能感受到它對容器鏡像的優化效果之明顯。第三篇將是進階篇,敬請期待吧!

使用jlink工具模塊化Java應用,結合Docker優化容器鏡像(下)

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