可用 jar, war, docker, GraalVM image 方式打包
jar
用 jar application 方式打包
<packaging>jar</packaging>
同時要加上
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
使用兩種指令打包,二選一
mvn package
打包到 target 目錄
mvn install
打包後,安裝到本地的 MAVEN repository
打包後,會產生兩個
test-0.0.1-SNAPSHOT.jar
這個可透過
java -jar test-0.0.1-SNAPSHOT.jar
啟動test-0.0.1-SNAPSHOT.jar.original
META-INF/MANIFEST.MF 內容為
Manifest-Version: 1.0
Created-By: Maven JAR Plugin 3.4.2
Build-Jdk-Spec: 17
Implementation-Title: test
Implementation-Version: 0.0.1-SNAPSHOT
Main-Class: org.springframework.boot.loader.launch.JarLauncher
Start-Class: com.test.test.TestApplication
Spring-Boot-Version: 3.4.0
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Spring-Boot-Classpath-Index: BOOT-INF/classpath.idx
Spring-Boot-Layers-Index: BOOT-INF/layers.idx
另一個 test-0.0.1-SNAPSHOT.jar.original,無法直接啟動,只是一個 jar
war
要先產生一個繼承 SpringBootServletInitializer 抽象類別
package com.test.test;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
public class ServletInitializer extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(TestApplication.class);
}
}
修改打包方式
<packaging>war</packaging>
排除 embedded tomcat
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
可同時支援 jar 跟 war
package com.test.test;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
@SpringBootApplication
public class TestApplication extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return startBuilder(builder);
}
public static void main(String[] args) {
// SpringApplication.run(TestApplication.class, args);
startBuilder(new SpringApplicationBuilder()).run(args);
}
private static SpringApplicationBuilder startBuilder(SpringApplicationBuilder builder) {
return builder.sources(TestApplication.class);
}
}
build 會產生 test-0.0.1-SNAPSHOT.war,可以放在 tomcat 或是用 java -jar 啟動
啟動 application
使用 java 指令
java -jar test-0.0.1-SNAPSHOT-exec.war
直接啟動
在 pom.xml 要修改設定
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<executable>true</executable>
</configuration>
</plugin>
</plugins>
</build>
然侯就能直接運作
./test-0.0.1-SNAPSHOT-exec.war
note: 在不支援的 OS 要修改 pom.xml
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<classifier>exec</classifier>
<executable>true</executable>
<embeddedLaunchScript>...</embeddedLaunchScript>
</configuration>
</plugin>
</plugins>
</build>
系統 service
可用 init.d 或 systemd service 啟動
init.d
在 /etc/init.d 建立一個 link
sudo ln -s test-0.0.1-SNAPSHOT.war /etc/init.d/test
然後就能用 service 啟動
sudo service test start
systemd
在 /etc/systemd/system 目錄中,建立檔案 test.service
[Unit]
Description-test
After=syslog.target
[Service]
User=test
ExecStart=/home/test/test-0.0.1-SNAPSHOT.war
# process receive SIGTERM signal will return 128+15=143 exit status code
SuccessExitStatus=143
[Install]
WantedBy=multi-user.target
然後
sudo systemctl start test
sudo systemctl enable test
Docker
docker -v
docker version
以 Dockerfile 產生 image
FROM eclipse-temurin:17-jre
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} application.jar
ENTRYPOINT ["java", "-Djava.security.egd=file:/dev/./urandom","-jar","/application.jar"]
EXPOSE 8080
eclipse-temurin 是 Eclipse 提供的 open JDK image
ARG 定義參數
用指令產生 image
docker build -t test/docker-all:1.0 .
-t
tags,image 的 name:tags
啟動
docker run -dp 8080:8080 test/docker-all:1.0
分層 image
定義分層的是 laryers.idx 檔案,內容為
- "dependencies":
- "BOOT-INF/lib/"
- "spring-boot-loader":
- "org/"
- "snapshot-dependencies":
- "application":
- "BOOT-INF/classes/"
- "BOOT-INF/classpath.idx"
- "BOOT-INF/layers.idx"
- "META-INF/"
一般會異動的只有 classes
建立新的 Dockerfile
FROM eclipse-temurin:17-jre as builder
WORKDIR application
ARG JAR_FILE=target/*-exec.jar
COPY ${JAR_FILE} application.jar
RUN java -Djarmode=layertools -jar application.jar extract
FROM eclipse-temurin:17-jre as builder
WORKDIR application
COPY --from-builder application/dependencies/ ./
COPY --from-builder application/spring-boot-loader/ ./
COPY --from-builder application/snapshot-dependencies/ ./
COPY --from-builder application/application/ ./
ENTRYPOINT ["java", "org.springframework.boot.loader.JarLauncher"]
EXPOSE 8081
注意 -Djarmode=layertools
layertools list
> java -Djarmode=layertools -jar test-0.0.1-SNAPSHOT.war list
dependencies
spring-boot-loader
snapshot-dependencies
application
產生 image
docker build -t test/docker-layer:1.0 .
分層建構不支援 executable jar,要修改 executable 設定
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<executable>false</executable>
</configuration>
</plugin>
</plugins>
</build>
Cloud Native Buildpacks
可透過 Maven/Gradle 產生 Cloud Native Buildpacks image
Cloud Native Buildpacks 是 2018 由 Pivotal, Heroku 發起,於10月加入Cloud Native沙盒,目標是要統一Buildpacks生態系。
修改 pom.xml
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>build-image</goal>
</goals>
</execution>
</executions>
<configuration>
<executable>false</executable>
<image>
<name>test/docker-cnb:${project.version}</name>
</image>
</configuration>
</plugin>
</plugins>
</build>
以指令產生
mvn clean package
也可以用 build-image target
mvn spring-boot:build-image
<configuration>
<executable>false</executable>
<image>
<name>test/docker-cnb:${project.version}</name>
<pullPolicy>IF_NOT_PRESENT</pullPolicy>
</image>
</configuration>
可加上 pullPolicy
ALWAYS
IF_NOT_PRESENT: 不存在基礎 image 時才拉取
NEVER
GraalVM image
Spring Boot 3.0 支援 GraalVM 22.3+, Native Build Tools Plugin 0.9.17+
GraalVM 是 2019 年 Oracle 發表的高性能,跨語言通用 VM,可提升 Java, Scala, Grooby, Kotlin 等基於 JVM 的 application。
GraalVM 有一個 Java 撰寫的高級 JIT compiler,借助 Truffle,可執行 Javascript, Ruby, Python
# Getting Started with Oracle GraalVM
產生 native-image
javac HelloWorld.java
native-image HelloWorld
./helloworld
Hello, World!
GraalVM applicaiton 跟傳統 application 的差異
對於 Java applicaiton,GraalVM 提供兩種運作方法
在 HotSpot VM 上使用 Graal 的 JIT compiler,一邊編譯,一邊運作
以 AOT 編譯的 GraalVM 原生 image 可執行檔案運作,是靜態預先編譯
GraalVM applcation 的差別
GraalVM image 會進行靜態分析
產生 image 時,無法執行到的 code 會被刪除,不會成為可執行檔案的一部分
GraalVM 不能直接偵測 code 的動態元素,ex: reflection, resource, 序列化, 動態代理,必須提前宣告設定
應用的 classpath 路徑是固定的,不能修改
不支援 bean 的延遲載入
某些地方不完全相容傳統的 java app
spring boot 產生 image 時
classpath 固定,無法變更
bean 不能被修改,不支援
@Profile
,@ConditionalOnProperty
Spring AOT 在產生 image 時,同時會產生
Java source code
byte code
GraalVM 相關 JSON 描述檔案,包含
resource-config.json Resource hints
reflection.config.json Reflection hints
serialization-config.json Serialization hints
proxy-config.json Java Proxy hints
jni-config.json JNI hints
從 https://start.spring.io/ 產生的 project,增加 Graal VM Nartive Support,maven pom.xml 裡面就包含了 native, native test 兩個 profiles
<plugins>
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
</plugin>
</plugins>
使用 maven 產生 image
mvn -Pnative spring-boot:build-image
可用 docker 執行
docker run --rm -p 8080:8080 docker.io/library/spring-boot-graalvm:1.0
也可以安裝 GraalVM Native Build Tools
sdk install java 23.0.1-graal
sdk use java 23.0.1-graal
用 maven
mvn -Pnative native:compile
會產生
spring-boot-graalvm: app 可執行檔案
spring-boot-graalvm.build_artifacts.txt: 說明 txt