乐趣区

Jib构建镜像的问题分析Could-not-find-or-load-main-class-startclass

问题简述

通过 Jib 插件将 SpringBoot 工程制作成 Docker 镜像成功,但是运行镜像的时候报错(Could not find or load main class ${start-class}),今天来一起分析这个问题,希望能帮读者跳过小坑。

关于 Jib 插件

在 Maven 工程中可以使用 Jib 插件将当前 Java 工程构建成 Docker 镜像,详情请参考:

  1. 《Docker 与 Jib(maven 插件版)实战》;
  2. 《Jib 使用小结(Maven 插件版)》;

环境信息

  1. 操作系统:macOS Mojave 10.14.6 (18G103)
  2. JDK:10.14.6 (18G103)
  3. Docker:10.14.6 (18G103)
  4. SpringBoot:2.1.8.RELEASE
  5. Jib 插件版本:1.6.1

源码下载

为了重现问题,我将出现问题的 SpringBoot 工程上传到 GitHub,地址和链接信息如下表所示:

名称 链接 备注
项目主页 https://github.com/zq2599/blo… 该项目在 GitHub 上的主页
git 仓库地址(https) https://github.com/zq2599/blo… 该项目源码的仓库地址,https 协议
git 仓库地址(ssh) git@github.com:zq2599/blog_demos.git 该项目源码的仓库地址,ssh 协议

这个 git 项目中有多个文件夹,本章的应用在 jib-error-demo 文件夹下,如下图红框所示:

问题:

  1. 在 pom.xml 文件所在目录执行命令 <font color=”blue”>mvn clean compile -U</font>,镜像可以构建成功,但是控制台输出了警告信息,如下图:

  1. 尝试用此镜像创建容器,行命令 <font color=”blue”>docker run –name=test bolingcavalry/hellojib:0.0.1-SNAPSHOT</font>,报错如下:
CN0014005932:~ zhaoqin$ docker run --name=test bolingcavalry/hellojib:0.0.1-SNAPSHOT
Error: Could not find or load main class ${start-class}
  1. <font color=”blue”>docker ps -a</font> 查看容器信息如下,只能看到状态是 ” 退出 ”,别的没啥了:
CN0014005932:~ zhaoqin$ docker ps -a
CONTAINER ID        IMAGE                                   COMMAND                  CREATED             STATUS                     PORTS               NAMES
d618f6588821        bolingcavalry/hellojib:0.0.1-SNAPSHOT   "java -Xms4g -Xmx4g …"   4 minutes ago       Exited (1) 4 minutes ago                       test
  1. 不甘心,用命令 <font color=”blue”>docker ps -a –no-trunc</font> 查看未截断的容器信息:
CN0014005932:~ zhaoqin$ docker ps -a --no-trunc
CONTAINER ID                                                       IMAGE                                   COMMAND                                                                           CREATED             STATUS                     PORTS               NAMES
d618f6588821f00d3bd0b67a85ff92988b90dfff710370c9d340d5c544c550af   bolingcavalry/hellojib:0.0.1-SNAPSHOT   "java -Xms4g -Xmx4g -cp /app/resources:/app/classes:/app/libs/* ${start-class}"   7 minutes ago       Exited (1) 7 minutes ago                       test
  1. 这次有新发现,容器启动时执行命令是 <font color=”blue”>java -Xms4g -Xmx4g -cp /app/resources:/app/classes:/app/libs/* ${start-class}</font>,怪哉!这个 <font color=”red”>\${start-class}</font> 是什么?我们来看一个正常镜像的启动命令:
java -Xms4g -Xmx4g -cp /app/resources:/app/classes:/app/libs/* com.bolingcavalry.jiberrordemo.JibErrorDemoApplication

如上所示,<font color=”blue”>com.bolingcavalry.jiberrordemo.JibErrorDemoApplication</font> 是 main 方法所在类,此命令可以正常运行 JibErrorDemoApplication 类的 main 方法;

  1. 小结问题:容器启动时执行 java 命令,把 <font color=”blue”>&dollar;{start-class}</font> 作为参数传给 java,导致 java 无法处理此参数,所以进程报错,导致容器退出;

问题原因

此问题的原因很简单:<font color=”blue”>java 工程中带有 main 方法的类不止一个 </font>,去查看 jib-error-demo 工程的代码,发现 Utils.java 中果然有个 main 方法:

public class Utils {public static String time(){return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()).toString();}

    public static void main(String[] args){System.out.println(time());
    }
}

将上述 main 方法删除掉,再构建镜像并运行容器,证实问题已经解决。

另一种解决问题的方法

如果不想动 Utils 类的代码 (也许 jar 包中某个类带有 main 方法),请打开 pom.xml 文件,在 jib 插件的配置中增加 <font color=”blue”>mainClass</font> 节点,节点内容是指定的 class 类,如下图红框所示:

经过上面的设置,问题也可以解决。

接下来,如果您有兴趣了解更深层次的原因,咱们一起来深度探险吧。

查找问题

  1. 这个问题在 Jib 的官方 GitHub 上是有记录的,先看第一条,地址是:https://github.com/GoogleCont…,如下图红框所示,同样的问题,最后 issue 的发起人自己关闭了这个 issue,因为他发现这自己的项目中有两个带有 main 方法的类:

  1. 再来看看这个 issue,https://github.com/GoogleCont…,Jib 的作者 Q Chen 推测是 Spring 将 <font color=”blue”>&dollar;{start-class}</font> 这个字符串设置为 Main-Class 属性的值(个人感觉,这里说的 Spring 应该是 spring boot 的 mave 插件吧),于是 Jib 插件在使用 Main-Class 的值得时候,拿到的就是 <font color=”blue”>&dollar;{start-class}</font> 这个字符串了:

  1. 170 这个 issue 的后续情节很有意思,Jib 作者 Q Chen 对这个问题也很纠结,如果 Java 工程中发现了多个带有 main 方法的类,Jib 究竟该如何处理呢?Q Chen 最后决定输出警告,如下图:

  1. 一起来看看这段代码吧,也就是上图中 #288,地址是:https://github.com/GoogleCont…,如果 mainClass 不像一个 class 类的名称,就输出警告,这个逻辑在 Gradle 和 Maven 插件中都写入了:

  1. 最后一个问题:上面代码中的 mainClass 从哪来的?打开上图的源码,地址是:https://github.com/GoogleCont…,如下图红框,从方法名可以推测,该值来自构建 SpringBoot 工程的 maven 插件,所以前面 Q Chen 提到 main-class 变量的值是 Spring 修改的,应该是来自这段代码:


此时的您,如果依然意犹未尽,咱们再来巩固一下 SpringBoot 的 start-class

关于 start-class

  1. 熟悉 SpringBoot 的同学其实对 <font color=”blue”>&dollar;{start-class}</font> 并不陌生,当工程中多个类中都有 <font color=”blue”>main</font> 方法时,使用该参数来指定 SpringBoot 的启动类;
  2. 先看 SpringBoot 官方文档熟悉一下 <font color=”blue”>start-class</font>,地址是:https://docs.spring.io/spring…,下图内容比较关键:我们设置的启动类被指定到 <font color=”blue”>Start-Class</font> 属性中,而 <font color=”blue”>Main-Class</font> 属性变成了 <font color=”blue”>org.springframework.boot.loader.JarLauncher</font>,这才是 SpringBoot 真正的启动类:

  1. 如下图,这是个补充说明,<font color=”blue”>Main-Class</font> 属性的值被转移到 <font color=”blue”>Start-Class</font> 属性这个动作,是 maven 插件在构建 jar 的时候做的:

  1. 所以 start-class 的值是来自 main-class,再看 main-class 的值从哪里来,如下图红框所示,maven 插件会去查找带有 <font color=”blue”>public static void main(String[] args)</font> 的类:


至此,Jib 构建的镜像问题分析完毕,一个小小的问题引发了这么多学习和探索,虽然有点费时间,但是可以让人再次感受到 ” 技术是相通的 ” 感觉,不知道您有没有这种感觉呢?

欢迎关注我的公众号:程序员欣宸

退出移动版