问题简述
经过Jib插件将SpringBoot工程制做成Docker镜像成功,可是运行镜像的时候报错(Could not find or load main class ${start-class}),今天来一块儿分析这个问题,但愿能帮读者跳太小坑。html
关于Jib插件
在Maven工程中可使用Jib插件将当前Java工程构建成Docker镜像,详情请参考:java
- 《Docker与Jib(maven插件版)实战》;
- 《Jib使用小结(Maven插件版)》;
环境信息
- 操做系统:macOS Mojave 10.14.6 (18G103)
- JDK:10.14.6 (18G103)
- Docker:10.14.6 (18G103)
- SpringBoot:2.1.8.RELEASE
- Jib插件版本:1.6.1
源码下载
为了重现问题,我将出现问题的SpringBoot工程上传到GitHub,地址和连接信息以下表所示:git
</br>程序员
这个git项目中有多个文件夹,本章的应用在jib-error-demo文件夹下,以下图红框所示:
github
问题:
- 在pom.xml文件所在目录执行命令<font color="blue">mvn clean compile -U</font>,镜像能够构建成功,可是控制台输出了警告信息,以下图:

- 尝试用此镜像建立容器,行命令<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}
- <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
- 不甘心,用命令<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
- 此次有新发现,容器启动时执行命令是<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方法; 6. 小结问题:容器启动时执行java命令,把<font color="blue">${start-class}</font>做为参数传给java,致使java没法处理此参数,因此进程报错,致使容器退出;spring
问题缘由
此问题的缘由很简单:<font color="blue">java工程中带有main方法的类不止一个</font>,去查看jib-error-demo工程的代码,发现Utils.java中果真有个main方法:docker
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方法删除掉,再构建镜像并运行容器,证明问题已经解决。shell
另外一种解决问题的方法
若是不想动Utils类的代码(也许jar包中某个类带有main方法),请打开pom.xml文件,在jib插件的配置中增长<font color="blue">mainClass</font>节点,节点内容是指定的class类,以下图红框所示:
通过上面的设置,问题也能够解决。app
接下来,若是您有兴趣了解更深层次的缘由,我们一块儿来深度探险吧。ssh
查找问题
- 这个问题在Jib的官方GitHub上是有记录的,先看第一条,地址是:https://github.com/GoogleContainerTools/jib/issues/1601 ,以下图红框所示,一样的问题,最后issue的发起人本身关闭了这个issue,由于他发现这本身的项目中有两个带有main方法的类:

- 再来看看这个issue, https://github.com/GoogleContainerTools/jib/issues/170 ,Jib的做者Q Chen推测是Spring将<font color="blue">${start-class}</font>这个字符串设置为Main-Class属性的值(我的感受,这里说的Spring应该是spring boot的mave插件吧),因而Jib插件在使用Main-Class的值得时候,拿到的就是<font color="blue">${start-class}</font>这个字符串了:

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

- 一块儿来看看这段代码吧,也就是上图中#288,地址是:https://github.com/GoogleContainerTools/jib/pull/228/files/c8757e1f9ea47edd78df18142de7836a68f22034 ,若是mainClass不像一个class类的名称,就输出警告,这个逻辑在Gradle和Maven插件中都写入了:

- 最后一个问题:上面代码中的mainClass从哪来的?打开上图的源码,地址是:https://github.com/GoogleContainerTools/jib/blob/c8757e1f9ea47edd78df18142de7836a68f22034/jib-maven-plugin/src/main/java/com/google/cloud/tools/jib/maven/BuildImageMojo.java ,以下图红框,从方法名能够推测,该值来自构建SpringBoot工程的maven插件,因此前面Q Chen提到main-class变量的值是Spring修改的,应该是来自这段代码:
此时的您,若是依然意犹未尽,我们再来巩固一下SpringBoot的start-class
关于start-class
- 熟悉SpringBoot的同窗其实对<font color="blue">${start-class}</font>并不陌生,当工程中多个类中都有<font color="blue">main</font>方法时,使用该参数来指定SpringBoot的启动类;
- 先看SpringBoot官方文档熟悉一下<font color="blue">start-class</font>,地址是:https://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/ ,下图内容比较关键:咱们设置的启动类被指定到<font color="blue">Start-Class</font>属性中,而<font color="blue">Main-Class</font>属性变成了<font color="blue">org.springframework.boot.loader.JarLauncher</font>,这才是SpringBoot真正的启动类:

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

- 因此start-class的值是来自main-class,再看main-class的值从哪里来,以下图红框所示,maven插件会去查找带有<font color="blue">public static void main(String[] args)</font>的类:
至此,Jib构建的镜像问题分析完毕,一个小小的问题引起了这么多学习和探索,虽然有点费时间,可是可让人再次感觉到"技术是相通的"感受,不知道您有没有这种感受呢?
欢迎关注个人公众号:程序员欣宸
