这里写自定义目录标题

  • 问题现象
  • 错误1
  • 错误2
  • 原因分析
  • 深入研究
  • spring-boot-maven-plugin
  • maven依赖的版本管理机制
  • parent指定为spring-boot-starter-parent
  • 在dependencyManagement中引入spring-boot-dependencies


问题现象

错误1

spring类型应用启动后报错,no main manifest attribute, in /app.jar

spring boot gradle打包可执行jar 找不到主类 springboot打包找不到main方法_spring boot

错误2

mvn build过程中,提示repackage failed

spring boot gradle打包可执行jar 找不到主类 springboot打包找不到main方法_spring_02

原因分析

大多数情况下,都是因为在spring应用的pom.xml中没有使用spring-boot-maven-plugin打包插件,或者插件的版本没有指定导致,需要在pom.xml里加入以下内容

<build>
    </plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>${spring-boot.version}</version>  //你的springboot的版本号是多少,这里就填多少
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>  //如果你的项目的parent是spring-boot-starter-parent,executions这一段也可以省略,因为在parent中已经指定了。反之则不可省
                        </goals>
                    </execution>
                </executions>
            </plugin>
    </plugins>
 </build>

深入研究

spring-boot-maven-plugin

spring-boot-maven-plugin是spring自带的一个用来打包的插件,其原理就是在mvn package的时候,将本应用自身的class,及他的依赖重新打包(repackage)成一个大的jar。只要安装指定版本jre,就可以通过java -jar xxx.jar运行,而不需要去系统的lib库中下载依赖,有兴趣看详情的可以看看spring-boot-maven-plugin原理,我们这里看下效果。

先建一个springboot的helloword项目,注意在pom里引入spring-boot-maven-plugin插件

public class CsdemoApplication {
    @RequestMapping("/")
    public String greeting() {
        return "Greeting!";
    }  
 
    public static void main(String[] args) {
        SpringApplication.run(CsdemoApplication.class, args);
    }
}

执行mvn clean package,在target目录下会生成一个jar文件和一个jar.original文件,original的还不到5K,其实这个是maven自带的打包工具对本工程自有代码进行打包的产物。而jar文件却有近26MB,这是spring-boot-maven-plugin对original进行repackage的产物。

spring boot gradle打包可执行jar 找不到主类 springboot打包找不到main方法_spring_03


把两个文件解压缩后对比,可以发现主要变化是jar里面多了一个lib文件夹,里面存储了本项目所依赖的所有jar。这个文件夹足足有25M,是导致整个jar包体积变大,以及为什么可以直接java -jar xxx.jar启动程序的原因。

spring boot gradle打包可执行jar 找不到主类 springboot打包找不到main方法_spring_04



spring boot gradle打包可执行jar 找不到主类 springboot打包找不到main方法_maven_05

写多了spring应用的同学估计注意到了,有时候可以省略${spring-boot.version}这一行,但是有时候就会报错,这又是为什么呢。

这个取决于你的项目的依赖关系,如果你的项目的parent是spring-boot-starter-parent(或其子项目),那么就可以在spring-boot-maven-plugin处省略version属性,mvn会自动去spring-boot-starter-parent中寻找相应的版本。

<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
</parent>

但如果你的项目的parent不是spring-boot-starter-parent(或其子项目),例如是继承于公司自己做的parent,那spring-boot-maven-plugin的version就必不可少,否则就会出现第一章中的错误。

maven依赖的版本管理机制

maven可以通过两种方式实现denpendencies模块中组件的版本统一管理,一种是整个项目parent指定为spring-boot-starter-parent,另一种是在dependencyManagement中引入spring-boot-dependencies,二者有一点点区别:

parent指定为spring-boot-starter-parent

如果你的项目的parent是spring-boot-starter-parent,那么在dependency中引入任何spring相关的starter时,均可以省略其版本号,原因就是在parent中已经引入了Spring Boot Dependencies,里面指定了大部分常用依赖的版本:

spring boot gradle打包可执行jar 找不到主类 springboot打包找不到main方法_spring_06


如果想在自己的应用中,修改某个依赖的版本,可以直接在properties中去指定:

<properties>
     <spring-data-releasetrain.version>Fowler-SR2</spring-data-releasetrain.version>
</properties>

如果要排除parent中默认使用的某个依赖,换成别的,可以按如下方式

<dependency>
      <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-data-rest</artifactId>
       <exclusions>
           <!-- 过滤lettuce,使用jedis作为redis客户端 -->
           <exclusion>
               <groupId>io.lettuce</groupId>
               <artifactId>lettuce-core</artifactId>
           </exclusion>
       </exclusions>
</dependency>
<dependency>
      <groupId>redis.clients</groupId>
       <artifactId>jedis</artifactId>
</dependency>

在dependencyManagement中引入spring-boot-dependencies

如果你的项目不是继承于spring-boot-starter-parent,而是通过dependencyManagement引入的sprintboot插件依赖,那么你依然可以在dependencies模块中省略各spring依赖的版本号,但是在build模块中就必须去指定版本号(比如前文所提到的spring-boot-maven-plugin就是在build模块下的)

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>${spring-boot.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

但这种方式,不能使用properties中直接去指定spring依赖的版本,而是需要在dependencyManagement里面的spring-boot-dependencies之前修改依赖的版本。例如同样修改Spring Data release train:

<dependencyManagement>
    <dependencies>
        <!-- Override Spring Data release train provided by Spring Boot -->
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-releasetrain</artifactId>
            <version>Fowler-SR2</version>
            <scope>import</scope>
            <type>pom</type>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>${spring-boot.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>