Spring Boot Maven Plugin打包异常及三种解决方法:Unable to find main class

【背景】spring-boot 项目,打包成可执行 jar,项目内有两个带有 main 方法的类并且都使用了@SpringBootApplication注解(或者另一种情形:你有两个 main 方法并且所在类都没有使用 @SpringBootApplication 注解),pom.xml 如下

<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <version>1.5.3.RELEASE</version>
    <executions>
        <execution>
            <goals>
                <goal>repackage</goal>
            </goals>
        </execution>
    </executions>
</plugin>

 

【问题】

  • 执行 mvn clean package,报错如下(说点不相关的,使用 install 同理。因为 spring-boot:repackage 目标(goal)(下文会说)被绑定在 package 构建阶段(phases),而 package 阶段在 install 阶段之前,指定构建阶段之前的阶段都会执行。详细参见:Introduction to the Build Lifecycle

  [ERROR] Failed to execute goal org.springframework.boot:spring-boot-maven-plugin:1.5.3.RELEASE:repackage (default) on project webapps-api-bid: Execution default of goal org.springframework.boot:spring-boot-maven-plugin:1.5.3.RELEASE:repackage failed: Unable to find a single main class from the following candidates [com.xx.api.main.ApiBidMain, com.xx.webapps.api.main.WebappsApiBidMain]

  • 执行 mvn clean package spring-boot:repackage,报错如下,不如上面日志详细

  [ERROR] Failed to execute goal org.springframework.boot:spring-boot-maven-plugin:1.5.3.RELEASE:repackage (default) on project webapps-api-bid: Execution default of goal org.springframework.boot:spring-boot-maven-plugin:1.5.3.RELEASE:repackage failed: Unable to find main class

 

 

【解决】

  Note:参考官网描述,没有指定 <mainClass> 或者继承了 spring-boot-starter-parent 并且 <start-class> 属性未配置时,会自动寻找签名是public static void main(String[] args)的方法... 所以插件懵逼了,两个妹子和谁在一起呢...

 

  • [推荐] 通用解决方法<configuration>下配置 mainClass,指定程序入口。
<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <version>1.5.3.RELEASE</version>
    <configuration>
        <mainClass>com.xx.webapps.api.main.WebappsApiBidMain</mainClass>
    </configuration>
    <executions>
        <execution>
            <goals>
                <goal>repackage</goal>
            </goals>
        </execution>
    </executions>
</plugin>

 

  Spring Boot Maven Plugin 提供了几个目标(goal),我们在<executions> 标签里配置的<goal>repackage</goal>对应spring-boot:repackage这个目标。

  • repackage: create a jar or war file that is auto-executable. It can replace the regular artifact or can be attached to the build lifecyle with a separate classifier.
  • run: run your Spring Boot application with several options to pass parameters to it.
  • start and stop: integrate your Spring Boot application to the integration-test phase so that the application starts before it.

  The plugin rewrites your manifest, and in particular it manages the Main-Class and Start-Class entries, so if the defaults don't work you have to configure those there (not in the jar plugin). The Main-Class in the manifest is actually controlled by the layout property of the boot plugin

  [译] 该插件重写了清单文件(MANIFEST.MF,也就是 jar 里面的清单文件),此文件管理着主类(Main-Class)和开始类(Start-Class)入口。清单文件中的 Main-Class 由 layout 控制

  这里的 Start-Class 就是我们配置的<mainClass>,而 Main-Class 受 layout 属性的控制,别被名字搞乱了(是不是很诡异?看看解决方法二就明白为啥如此诡异了).... 来张图直观的感受下,对应使用上面 xml 配置打包后的清单文件(MANIFEST.MF):

  

 

 

  layout 属性默认不需要配置,插件会自动推断。不同的 layout 属性清单文件里面的 Main-Class 也会相应的不同。比如 layout 不配置或者配置为 JAR 对应的 Main-Class 是 JarLauncher,layout 配置为 WAR 对应的 Main-Class 是 WarLauncher。

 

  • [有限制条件] 解决方法二如果你的 pom 继承自spring-boot-starter-parent(注意此前提),也可以直接在 <properties> 配置 <start-class>(其实这里的 start-class 直接对应清单文件里的 Start-Class)
<properties>
    <start-class>com.xx.webapps.api.main.WebappsApiBidMain</start-class>
</properties>

 

  • 解决方法三:打包的的时候注释掉其他的 @SpringBootApplication... 或者你有两处 main 方法并且都没有使用@SpringBootApplication注解,注释掉一个 main 方法..... 这就是第三种解决方法 233333

 

 

【随便说说】

  说说spring-boot:repackage 这个目标。Spring Boot Maven Plugin 这个插件包含一系列目标(goal),我们在<executions>标签里配置的<goal>repackage</goal>对应 spring-boot:repackage 这个目标,看下官方介绍

  spring-boot:repackage repackages your jar/war to be executable.

  Repackages existing JAR and WAR archives so that they can be executed from the command line using java -jar. With layout=NONE can also be used simply to package a JAR with nested dependencies (and no main class, so not executable).

 

  简单点说,这货重新打包个可执行的 jar/war,可以在命令行使用 -jar 执行。如果指定 layout 为 NONE 那就没有主类只是打个普通的 jar(不可执行),一般不会这么做。

  一般情况,这个目标会打一个新的 jar/war,并把 maven 默认打的 jar/war 添加.original 后缀,在 target 目录下可以看到:

  

 

 【参考】

1.https://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#build-tool-plugins-maven-packaging

2.https://docs.spring.io/spring-boot/docs/2.0.0.BUILD-SNAPSHOT/maven-plugin//repackage-mojo.html

3.https://stackoverflow.com/questions/23217002/how-do-i-tell-spring-boot-which-main-class-to-use-for-the-executable-jar