问题简述

通过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/blog_demos 该项目在GitHub上的主页
git仓库地址(https) https://github.com/zq2599/blog_demos.git 该项目源码的仓库地址,https协议
git仓库地址(ssh) git@github.com:zq2599/blog_demos.git 该项目源码的仓库地址,ssh协议


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

问题:

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

  2. 尝试用此镜像创建容器,行命令docker run --name=test bolingcavalry/hellojib:0.0.1-SNAPSHOT,报错如下:
CN0014005932:~ zhaoqin$ docker run --name=test bolingcavalry/hellojib:0.0.1-SNAPSHOT
Error: Could not find or load main class ${start-class}
  1. docker ps -a查看容器信息如下,只能看到状态是"退出",别的没啥了:
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. 不甘心,用命令docker ps -a --no-trunc查看未截断的容器信息:
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. 这次有新发现,容器启动时执行命令是java -Xms4g -Xmx4g -cp /app/resources:/app/classes:/app/libs/* ${start-class},怪哉!这个${start-class}是什么?我们来看一个正常镜像的启动命令:
java -Xms4g -Xmx4g -cp /app/resources:/app/classes:/app/libs/* com.bolingcavalry.jiberrordemo.JibErrorDemoApplication

如上所示,com.bolingcavalry.jiberrordemo.JibErrorDemoApplication是main方法所在类,此命令可以正常运行JibErrorDemoApplication类的main方法;

6. 小结问题:容器启动时执行java命令,把${start-class}作为参数传给java,导致java无法处理此参数,所以进程报错,导致容器退出;

问题原因

此问题的原因很简单:java工程中带有main方法的类不止一个,去查看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插件的配置中增加mainClass节点,节点内容是指定的class类,如下图红框所示:



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

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

查找问题

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

  2. 再来看看这个issue, https://github.com/GoogleContainerTools/jib/issues/170 ,Jib的作者Q Chen推测是Spring将${start-class}这个字符串设置为Main-Class属性的值(个人感觉,这里说的Spring应该是spring boot的mave插件吧),于是Jib插件在使用Main-Class的值得时候,拿到的就是${start-class}这个字符串了:

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

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

  5. 最后一个问题:上面代码中的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

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

  3. 如下图,这是个补充说明,Main-Class属性的值被转移到Start-Class属性这个动作,是maven插件在构建jar的时候做的:

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



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

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

Jib构建镜像的问题分析(Could not find or load main class ${start-class})的更多相关文章

  1. 利用Google开源Java容器化工具Jib构建镜像

    转载:https://blog.csdn.net/u012562943/article/details/80995373 一.前言 容器的出现让Java开发人员比以往任何时候都更接近“编写一次,到处运 ...

  2. Jib插件构建镜像push到阿里云镜像仓库

    一.前言 Jib:Google开源的Java容器化工具 可作为插件快速集成到项目中,构建镜像,实现 Java 应用容器化 下面贴出一张从网上看到的Jib描述~ 二.利用Jib插件构建镜像push到阿里 ...

  3. Dockerfile 构建镜像 - 每天5分钟玩转容器技术(13)

    Dockerfile 是一个文本文件,记录了镜像构建的所有步骤. 第一个 Dockerfile 用 Dockerfile 创建上节的 ubuntu-with-vi,其内容则为: 下面我们运行 dock ...

  4. 第 3 章 镜像 - 013 - Dockerfile 构建镜像

    第一个 Dockerfile FROM ubuntu RUN apt-get update && apt-get install -y vim 运行 docker build 命令构建 ...

  5. 11-Dockerfile构建镜像

    用 Dockerfile 创建上节的 ubuntu-with-vi,其内容则为: FROM ubuntu RUN apt-get update && apt-get install v ...

  6. Docker容器学习梳理 - Dockerfile构建镜像

    在Docker的运用中,从下载镜像,启动容器,在容器中输入命令来运行程序,这些命令都是手工一条条往里输入的,无法重复利用,而且效率很低.所以就需要一 种文件或脚本,我们把想执行的操作以命令的方式写入其 ...

  7. docker入门——构建镜像

    前面我们已经介绍了如何拉取已经构建好的带有定制内容的Docker镜像,那么如何构建自己的镜像呢? 构建Docker镜像有以下两种方法: 使用docker commit命令. 使用docker buil ...

  8. Ubuntu18.04安装Docker并部署(编译、发布、构建镜像)Asp.NetCore项目全过程笔记

      环境准备:阿里云Ubuntu18.04 全新安装   一.安装Docker 1.删除旧版本并更新包索引: sudo apt-get remove docker docker-engine dock ...

  9. docker之构建镜像

    构建Docker镜像有以下两种方法: 使用docker commit命令. 使用docker build命令和 Dockerfile 文件. 在这里并不推荐使用docker commit来构建镜像,而 ...

随机推荐

  1. 关于line-height 行高的一些理解和技巧

    大家都知道,如何设置文字垂直居中,也就是:设置line-height 和 外围盒子的高度height一致: 其实这里有个地方,是多余的,也就是height,设不设置都居中: 那么,行高是生产高度的? ...

  2. jvm系列(五):Java GC 分析

    Java GC就是JVM记录仪,书画了JVM各个分区的表演. 什么是 Java GC Java GC(Garbage Collection,垃圾收集,垃圾回收)机制,是Java与C++/C的主要区别之 ...

  3. 设计模式(C#)——02工厂模式

    推荐阅读:  我的CSDN  我的博客园  QQ群:704621321       在简单工厂模式中讲到简单工厂模式的缺点:难以扩展,一旦添加新运算就必须修改简单工厂方法.       工厂方法模式: ...

  4. CentOS 7下安装配置搭建jdk+tomcat+MariaDB环境

    1.JDK安装 注意:rpm与软件相关命令 相当于window下的软件助手 管理软件 步骤: 1)查看当前Linux系统是否已经安装java 输入 rpm -qa | grep java 2)卸载两个 ...

  5. java web基础 js、JSP、servlet之间的传递

    @ JS 与 JSP :JSP无法直接获取JS的值,只能通过隐藏表单或者dom节点设置. JSP中设置隐藏表单input,或者设置任意一个隐藏或者不隐藏的节点比如div, 而JS就通过document ...

  6. 第五章 Spring核心概念

     5.1.1 企业级应用开发    企业级应用是指那些为商业组织,大型企业而创建部署的解决方案及应用,大型企业级应用的结构复杂,涉及的外部资源众多,事务密集,数据规模大,用户数量多,有较强的安全性考虑 ...

  7. js中尺寸类样式

    js中尺寸类样式 一:鼠标尺寸类样式 都要事件对象的配合 Tip:注意与浏览器及元素尺寸分开,鼠标类尺寸样式都是X,Y,浏览器及元素的各项尺寸时Height,Width 1:检测相对于浏览器的位置:e ...

  8. postman 接口参数化操作

    最近一直忙于AI模型的准确率任务中,这种操作需要大量的数据才能计算出模型的准确率.所以这里问遇到的问题和之前数据随机参数化有点不同,之前的参数比如说用户姓名或用户身份证号,这样数据可以在postman ...

  9. os.linesep提取当前平台使用的换行符

    1. unix平台的换行符:\n 2.DOS/Win32平台的换行符:\r\n 3.通过os.linesep函数可以提取当前所处平台的换行符,从而实现不需要关注程序运行在什么平台,也不需要根据不同的平 ...

  10. 牛客国庆集训派对Day6 A Birthday 费用流

    牛客国庆集训派对Day6 A Birthday:https://www.nowcoder.com/acm/contest/206/A 题意: 恬恬的生日临近了.宇扬给她准备了一个蛋糕. 正如往常一样, ...