Spring Boot 项目 Dockerfile 调整优化备忘 —— Could not find or load main class PropertiesLauncher
status
Published
type
Post
slug
spring-boot-3.2-dockerfile-change
date
Oct 10, 2024
tags
Docker
Java
Spring
Note
summary
文章说明了 Spring Boot 3.2 版本将 PropertiesLauncher 和 JarLauncher 类移至org.springframework.boot.loader.launch 包。这影响了使用解压 fat JAR 方式构建 Docker 镜像的 Dockerfile,需更新 ENTRYPOINT 命令中的类路径。java -jar 启动方式不受影响。建议利用 Spring Boot 分层 JAR 特性和 Docker 多阶段构建优化镜像,并记得关注 Release Notes 以便跟进相应变更。
TLDR
从 Spring Boot 3.2 版本开始,
PropertiesLauncher
或者JarLauncher
这两个类的位置发生了变化,它们现在被统一放在了原路径下新定义的 launch 包下,即:org.springframework.boot.loader.launch.PropertiesLauncher
。因此,之前编写的 Dockerfile 中也需要相应地更新这一引用,以避免出现
Could not find or load main class org.springframework.boot.loader.PropertiesLauncher
的错误 。当然如果之前并没有在 Dockerfile 中将初步编译的 fat JAR 解压提取的操作,那这个问题就不会出现,毕竟 (大概
java -jar
的启动命令一般还是不会有变化的。Spring Boot 项目 Dockerfile 编写
简单 Dockerfile 示例
一个简单的 Dockerfile 如下即可:
FROM eclipse-temurin:17-jdk-alpine ARG JAR_FILE=target/*.jar COPY ${JAR_FILE} app.jar ENTRYPOINT ["java","-jar","/app.jar"]
考虑到 Docker 镜像分层的的特性,而 Spring Boot 构建得到的 fat JAR 本身也是有层次的,可以调整 Dockerfile 至如下:
FROM eclipse-temurin:17-jdk-alpine ARG DEPENDENCY=target/dependency COPY ${DEPENDENCY}/BOOT-INF/lib /app/lib COPY ${DEPENDENCY}/META-INF /app/META-INF COPY ${DEPENDENCY}/BOOT-INF/classes /app ENTRYPOINT ["java","-cp","app:app/lib/*","hello.Application"]
现在即分成了三层,应用资源在后两层。如果依赖关系没变,那么第一层(/BOOT-INF/lib)就不需要变动,可以直接利用缓存机制加快构建速度以及运行启动速度。
Spring Boot 的分层 JAR 文件
自 Spring Boot 2.3 版本开始,使用 Spring Boot Maven 或 Gradle 插件构建的 JAR 文件中就包含了分层信息。这些分层信息将应用程序的不同部分根据它们在应用程序构建之间可能发生变化的可能性进行分离。基于这一特性可以使得 Docker 镜像分层更加高效。
mkdir target/extracted java -Djarmode=layertools -jar target/*.jar extract --destination target/extracted
利用这一特性,我们可以优化 Dockerfile,使得构建过程更加快速,同时生成的 Docker 镜像也更小。这对于频繁更新和部署的微服务应用尤其有益。一个示例如下:
FROM eclipse-temurin:17-jdk-alpine ARG EXTRACTED=/project/app/target/extracted COPY ${EXTRACTED}/dependencies/ ./ COPY ${EXTRACTED}/spring-boot-loader/ ./ COPY ${EXTRACTED}/snapshot-dependencies/ ./ COPY ${EXTRACTED}/application/ ./ ENTRYPOINT ["java","org.springframework.boot.loader.launch.JarLauncher"]
JarLauncher
已从 fat JAR 中提取出来,可直接拿来用于启动应用程序,而无需像上面一样使用主函数名称硬编码作为应用启动入口。这次遇到的问题也就在于此处作为启动入口的
JarLauncher
/PropertiesLauncher
位置发生了变化。还是要多注意 Release 信息啊!多阶段构建 Dockerfile 示例
利用 Docker 的多阶段构建特性,最终的 Dockerfile 示例如下:
FROM eclipse-temurin:17-jdk-alpine AS build WORKDIR /app ARG JAR_FILE=target/*.jar COPY ${JAR_FILE} app.jar RUN java -Djarmode=layertools -jar app.jar extract --destination extracted FROM eclipse-temurin:17-jre-alpine RUN addgroup -S app && adduser -S app -G app USER app:app WORKDIR /app COPY --from=build /app/extracted/dependencies/ ./ COPY --from=build /app/extracted/spring-boot-loader/ ./ COPY --from=build /app/extracted/snapshot-dependencies/ ./ COPY --from=build /app/extracted/application/ ./ ENTRYPOINT ["java", "org.springframework.boot.loader.launch.JarLauncher"]
当然还可以将 Maven 或 Gradle 构建的过程也加入到多阶段构建中,此处就不做展开了。