2025/08/04

Spring Boot 3 打包

可用 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