侧边栏壁纸
博主头像
孔子说JAVA博主等级

成功只是一只沦落在鸡窝里的鹰,成功永远属于自信且有毅力的人!

  • 累计撰写 285 篇文章
  • 累计创建 125 个标签
  • 累计收到 4 条评论

目 录CONTENT

文章目录

SpringBoot打包插件spring-boot-maven-plugin详解

孔子说JAVA
2022-05-12 / 0 评论 / 0 点赞 / 308 阅读 / 14,436 字 / 正在检测是否收录...

许多公司都会使用springboot作为服务应用开发框架,springboot框架提供了一套自己的打包机制,是通过 spring-boot-maven-plugin 插件来实现的。使用该 maven 插件打包时,将应用程序及其依赖jar一起打包到一个独立的jar包/war包中,打出来的是fat jar,可以直接启动。

1、pom.xml中添加插件

1.1 添加spring-boot-maven-plugin插件

对于新建的一个springboot项目来说,pom.xml的 <build> - <plugins>中会加入 spring-boot-maven-plugin 插件:

<build>
  <plugins>
    <plugin>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-maven-plugin</artifactId>
      <version>2.2.1.RELEASE</version>
    </plugin>
  </plugins>
</build>
  • 使用2.2.1.RELEASE版本需要maven版本在2.0及以上,JDK在1.8及以上。

1.2 功能说明

通过idea可以看到maven中包含了 spring-boot-maven-plugin 插件:

image-1652142162115

  • build-info:生成项目的构建信息文件 build-info.properties
  • help:用于展示 spring-boot-maven-plugin 的帮助信息。使用命令行 mvn spring-boot:help -Ddetail=true -Dgoal=<goal-name> 可展示goal的参数描述信息。
  • repackage:默认是 goal,在 mvn package 执行之后,这个命令再次打包生成可执行的 jar,同时将 mvn package 生成的 jar 重命名为 *.origin。也可以生成war包。
  • run:用来运行 Spring Boot 应用。
  • start:在 mvn integration-test 集成测试阶段,进行 Spring Boot 应用生命周期的管理。
  • stop:这个在 mvn integration-test 集成测试阶段,进行 Spring Boot 应用生命周期的管理。

spring-boot-maven-plugin 插件默认在父工程 sprint-boot-starter-parent 中被指定为 repackage,可以点击 sprint-boot-starter-parent 进入父pom进行查看,如下图:

image-1652142941068

如果需要设置其他属性,需要在当前应用的pom.xml中进行设置。

2、打包操作

2.1 打可执行jar包

springboot项目构建jar包或war包的命令为 repackage,作用于maven生命周期的package阶段,在 mvn package 执行之后,这个命令会再次打包生成可执行的包,例如打jar包时,生成可执行jar包,同时将 mvn package 生成的 jar 重命名为 *.origin。默认情况下,repackage会将工程中引入的任何依赖打到包中。

  • 使用 spring-boot-maven-plugin 插件打出的可执行jar不建议作为jar给其他服务引用,因为可能出现访问可执行jar中的一些配置文件找不到的问题。
  • 打包主要使用的是 repackage goal,它是spring-boot-starter-parent为插件设置的默认goal。这个goal绑定在 maven 的 package 生命周期上,完整命令为 mvn package spring-boot:repackage。在 mvn package 执行打包之后,repackage 再次打包生成可执行的 jar包或war包。

命令行执行打包命令 mvn clean package,即可打jar包(插件默认打jar包),target文件夹里的 *.jar即为可执行jar包。

  • 也可以只执行package命令 mvn package

或者通过开发工具如idea执行clean和package俩命令:

image-1652143170293

执行以上命令时会自动触发spring-boot-maven-plugin插件的repackage目标,完后可以在target目录下看到生成的jar,如下图:

image-1652143191708

可以看到生成了两个jar相关的文件,其中 common.jarspring-boot-maven-plugin 插件重新打包后生成的可执行jar,即可以通过 java -jar common.jar 命令启动。common.jar.original 这个则是 mvn package 打包的原始jar,在 spring-boot-maven-plugin 插件 repackage 命令操作时重命名为xxx.original,这是一个普通的jar,可以被引用在其他服务中。

2.2 打依赖包

使用spring-boot-maven-plugin插件打包时,默认生成两个包,以打jar包为例,生成的是 *.jar*.jar.original

  • 这是因为spring-boot-maven-plugin的 rapackage 目标,是在 mvn package 执行之后,再次打包生成可执行的 jar包。repackage生成jar包的名称与 mvn package 生成的原始包名称相同,而原始包被重命名为 *.origin。这样生成的 *.jar 可直接运行,但不能被其他项目模块依赖。这是因为repackage将项目的class都放在了jar包 BOOT-INF/classes 文件夹中,导致其他模块不能加载jar包中的class。

如果想让构建出来的jar包可以被其他项目模块依赖,有以下2种方式:

方式一:不使用spring-boot-maven-plugin打包

使用该方式不会生成可执行jar,有以下3种方式:

  • 将spring-boot-maven-plugin注释掉

  • 将spring-boot-maven-plugin的repackage目标跳过,即设置skip为true

<project>
	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
				<executions>
					<execution>
						<id>repackage</id>
<!--                        引入 spring-boot-starter-parent 的项目可注释掉 -->                        
<!--                        <goals> -->
<!--                            <goal>repackage</goal> -->
<!--                        </goals> -->
						<configuration>
							<skip>true</skip>
						</configuration>
					</execution>
				</executions>
			</plugin>
		</plugins>
	</build>
</project>
  • 使用命令行跳过spring-boot-maven-plugin的repackage
mvn clean package -Dspring-boot.repackage.skip=true

方式二:使用spring-boot-maven-plugin打包,配置classifier参数

官方提供的解决办法:在spring-boot-maven-plugin插件上配置classifier参数,指定可执行jar包的名称后缀,例如将classifier设置为 myexec,则 mvn package 生成的原始jar包不被repackage目标重命名,可执行jar包的名称变为*-myexec.jar。这样原始jar包就可以被其他项目依赖啦。

<build>
  <plugins>
    <plugin>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-maven-plugin</artifactId>
      <executions>
        <execution>
          <id>repackage</id>
          <goals>
            <goal>repackage</goal>
          </goals>
          <configuration>
            <!-- 指定该jar包启动时的主类[建议],可以不配置 -->
            <mainClass>com.common.util.CommonUtilsApplication</mainClass>
            <!-- 配置的 classifier 追加在可执行jar包名字的后面,在插件执行 repackage 命令时,
    就不会给 mvn package 所打成的 jar 重命名了,原始jar可以被其他项目引用-->
            <classifier>myexec</classifier>
          </configuration>
      	 <execution>
      </executions>
    </plugin>
  </plugins>
</build>

效果如下:

image-1652144606673

2.3 打包时排除provided依赖

项目中scope为provided的依赖,比如 lombok、mybatis-plus 等,只作用于编译阶段,编译完成就可以功成身退了。在spring maven打包时,provided依赖会排除在包外,但springboot maven打包时,还会将这些依赖打进 war 包的 lib-provided 文件夹里 或 jar 包的 lib 文件夹(jar包中的BOOT-INF/lib目录)里。如果想要在jar包中排除provided的依赖,可以通过以下两种方式。

<dependency>
  <groupId>org.projectlombok</groupId>
  <artifactId>lombok</artifactId>
  <scope>provided</scope>
</dependency>

如上所示,首先需要设置依赖包的scope为provided,然后通过以下2种方式可以将gai依赖排除在jar包外。

方式一:通过指定groupId和artifactId排除某个特定的依赖

<build>
   <plugins>
       <plugin>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-maven-plugin</artifactId>
           <configuration>
               <excludes>
                   <exclude>
                       <groupId>org.projectlombok</groupId>
                       <artifactId>lombok</artifactId>
                   </exclude>
               </excludes>
           </configuration>
       </plugin>
   </plugins>
</build>

方式二:通过指定groupId排除groupId相关的所有依赖

<build>
   <plugins>
       <plugin>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-maven-plugin</artifactId>
           <configuration>
               <excludeGroupIds>org.projectlombok</excludeGroupIds>
           </configuration>
       </plugin>
   </plugins>
</build>

配置后,重新执行: mvn clean package, lombok的jar已经不在xxx.jar里面,应用也可以正常启动: mvn spring-boot:run 或者 java -jar xxx.jar

2.4 部署原始包并生成本地可执行包

spring-boot-maven-plugin的 repackage 默认用可执行包代替原始包。如果项目需要部署原始包,但也需要生成可执行包供本地运行,可配置attach为false。此时也生成原始包和可执行包,只有原始包会被安装部署到本地仓库。

<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <version>2.2.1.RELEASE</version>
    <executions>
        <execution>
        <id>repackage</id>
        <goals>
        	<goal>repackage</goal>
        </goals>
        <configuration>
        	<attach>false</attach>
        </configuration>
        </execution>
    </executions>
</plugin>

查看本地仓库中的JAR,可以看到体积很小,本例为529KB,为原始包,如下图。

image-1652148120796

对比下项目模块target目录下输出的jar包,为可执行jar包,可以看到比本地仓库中的体积大了很多,本例为28544KB,如下图所示:

image-1652148240781

2.5 设置active profiles

方式一:设置profile参数

设置spring-boot-maven-plugin插件的profile参数,以下示例将 active profiles 设置为 foo 和 bar。

<build>
    ...
    <plugins>
        ...
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>
              <profiles>
                <profile>foo</profile>
                <profile>bar</profile>
              </profiles>
            </configuration>
            ...
        </plugin>
        ...
    </plugins>
    ...
</build>

使用命令行,注意profile间用英文逗号分隔

mvn spring-boot:run -Dspring-boot.run.profiles=foo,bar

方式二:指定jvm参数

<build>
    ...
    <plugins>
      ...
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
        <configuration>
          <jvmArguments>
            -Dspring.profiles.active=foo,bar
          </jvmArguments>
        </configuration>
        ...
      </plugin>
      ...
    </plugins>
    ...
</build>

使用命令行指定

mvn spring-boot:run -Dspring-boot.run.jvmArguments="-Dspring.profiles.active=foo,bar"

2.6 添加系统属性和环境变量

2.6.1 添加系统属性

通过配置systemPropertyVariables 来设置系统属性(system property)。以下示例将 property1 设置为 test,将 property2 设置为 42,将property3设置为空字符串。

<build>
    <properties>
        <my.value>42</my.value>
    </properties>
    ...
    <plugins>
        ...
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>
                <systemPropertyVariables>
                    <!--	property1='test'	-->
                    <property1>test</property1>
                    <!--	property2=42	-->
                    <property2>${my.value}</property2>
                    <!--	property3=''	-->
                    <property3/>
                    <!--	property4=''	-->
                    <property4></property4>
                </systemPropertyVariables>
            </configuration>
            ...
        </plugin>
        ...
    </plugins>
    ...
</build>
  • 如果属性值为空,或未指定,如上例中的 <property4>和<property3/>,系统将把空字符串赋值给属性
  • 赋值时,maven将剪裁掉值两端的空格。因此不能赋以空格开头或结尾的值。如需要这样的值,可以考虑用jvm参数代替,systemPropertyVariables通过设置 jvmArguments 来设置jvm参数。
  • 可用字符串类型的maven变量给系统属性赋值,例如 <property2>${my.value}</property2>。其他类型的maven变量,如List、URL,不会被计算,只会按字面量赋值。
  • jvmArguments 参数的优先级比系统属性高,如下示例中,在下面的示例中,property1的值将被覆盖为“overridden”
mvn spring-boot:run -Dspring-boot.run.jvmArguments="-Dproperty1=overridden"

2.6.2 设置环境变量(environment variables)

通过配置environmentVariables 来设置环境变量(environment variables)。以下示例将ENV1设置为 5000,将 ENV2设置为Some Text,将ENV3、ENV4设置为空字符串。

<build>
    ...
    <plugins>
        ...
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>
                <environmentVariables>
                    <!--	ENV1=5000	-->
                    <ENV1>5000</ENV1>
                    <!--	ENV2='Some Text'	-->
                    <ENV2>Some Text</ENV2>
                    <!--	ENV3=''	-->
                    <ENV3/>
                    <!--	ENV4=''	-->
                    <ENV4></ENV4>
                </environmentVariables>
            </configuration>
            ...
        </plugin>
        ...
    </plugins>
    ...
</build>
  • 如果变量值为空,或未指定,如上例中的 <ENV4></ENV4>和<ENV3/>,系统将把空字符串赋值给变量
  • 赋值时,maven将剪裁掉值两端的空格。因此不能赋以空格开头或结尾的值
  • 可用字符串类型的maven变量给系统属性赋值。其他类型的maven变量,如List、URL,不会被计算,只会按字面量赋值
  • 以这种方式设置的环境变量会覆盖已有值

2.7 debug调试程序

spring-boot-maven-plugin的 run goal 可启动程序运行项目。但发现直接执行 spring-boot:run 时,不能debug打断点调试。spring-boot-maven-plugin的官方文档给出了原因和解决方式。

原因

run goal 默认将应用运行于 a forked process,通常方式设置的命令行参数不会作用于程序,因此直接使用run goal,是不能调试程序的。

解决方式

若想调试,或想设置其他JVM 参数,应配置jvmArguments参数。

方式一:在pom文件中配置

<project>
  ...
  <build>
    ...
    <plugins>
      ...
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
        <version>2.2.1.RELEASE</version>
        <configuration>
          <jvmArguments>
            -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005
          </jvmArguments>
        </configuration>
        ...
      </plugin>
      ...
    </plugins>
    ...
  </build>
  ...
</project>

方式二:在pom文件中配置

mvn spring-boot:run -Dspring-boot.run.jvmArguments="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005"

通过以上方式运行后,进程将监听本地的5005端口,等待调试器连接进来。我们可以在5005端口进行远程调试。

在idea中实践

  1. 在pom中按上述代码配置jvmArguments参数。

  2. 添加一个远程调试配置:

    • Host为 localhost,Port 应与spring-boot-maven-plugin中配置的端口一致,这里是5005

image-1652148983031

  1. 执行spring-boot-maven-plugin的 run goal

image-1652149028657

这时可以看到,进程在监听5005端口,等待调试器连接

image-1652149058907

  1. DEBUG模式下启动远程调试,发现已连接上5005端口

image-1652149085605

  1. 可以打断点调试了

3、运行run

run goal 可运行springboot项目,其默认将应用运行于 a forked process,通常方式设置的命令行参数不会对 run goal 启动的进程产生影响。

  • 直接使用 run goal,不能使程序进入调试模式,原因如上。应在插件中配置jvmArguments,调试程序的配置参考:debug调试程序
  • 设置系统属性和环境变量的方法: 添加系统属性和环境变量
  • 设置active profiles的方法:设置active profiles
  • 当将fork 属性为置为false时,run goal将直接通过 Maven JVM 运行项目(非官方建议做法),而不使用 a forked process。此时通过spring-boot-maven-plugin插件设置的的JVM参数(jvmArguments), 系统属性(systemPropertyVariables),环境变量(environmentVariables)和代理 (agents )将不生效。
  • 插件支持资源热更新,通过将addResources置为true来启用热更新
<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <configuration>
        <addResources>true</addResources>
    </configuration>
</plugin>

启用热更后,运行时,src/main/resources 文件夹被加入类路径,target/classes 文件夹下的副本被删除。

构建项目的资源筛选不作用于热更!

Spring Boot 1.3以后,官方推出了spring-boot-devtools工具,这个工具在热更支持上体验更好,所以spring-boot-maven-plugin的热更功能默认是关闭的。

  • repackage goal 一致,run goal也将所有的依赖都放在类路径中,包括scope为provided的依赖
  • 若想在运行时包含scope为test的依赖,可以将useTestClasspath设置为true。注意,这只影响run goal 不影响repackage goal,即打包时不会包含scope为test的依赖。

4、jar内部结构

对这两个jar文件解压看看里面的结构差异:

4.1 common.jar目录结构

image-1652144401013

其中BOOT-INF主要是一些启动信息,包含classes和lib文件,classes文件放的是项目里生成的字节文件class和配置文件,lib文件是项目所需要的jar依赖。

META-INF目录下主要是maven的一些元数据信息,MANIFEST.MF文件内容如下:

Manifest-Version: 1.0
Implementation-Title: java-common-utils
Implementation-Version: 0.0.1-SNAPSHOT
Start-Class: com.common.util.CommonUtilsApplication
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Build-Jdk-Spec: 1.8
Spring-Boot-Version: 2.1.9.RELEASE
Created-By: Maven Archiver 3.4.0
Main-Class: org.springframework.boot.loader.JarLauncher

其中Start-Class是项目的主程序入口,即main方法。Springboot-Boot-Classes和Spring-Boot-Lib指向的是生成的BOOT-INF下的对应位置。

Main-Class属性值为 org.springframework.boot.loader.JarLauncher,这个值可以通过设置属性layout来控制,如下:

<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <configuration>
        <!--使用-Dloader.path需要在打包的时候增加<layout>ZIP</layout>,不指定的话-Dloader.path不生效-->
        <layout>ZIP</layout>
        <!-- 指定该jar包启动时的主类[建议] -->
        <mainClass>com.common.util.CommonUtilsApplication</mainClass>
    </configuration>
    <executions>
        <execution>
            <goals>
                <goal>repackage</goal>
            </goals>
        </execution>
    </executions>
</plugin>

设置<layout>ZIP</layout>时 Main-Class 为 org.springframework.boot.loader.PropertiesLauncher,具体layout值对应Main-Class关系如下:

  • JAR,即通常的可执行jar
Main-Class: org.springframework.boot.loader.JarLauncher
  • WAR,即通常的可执行war,需要的servlet容器依赖位于WEB-INF/lib-provided
Main-Class: org.springframework.boot.loader.warLauncher
  • ZIP,即DIR,类似于JAR
Main-Class: org.springframework.boot.loader.PropertiesLauncher
  • MODULE,将所有的依赖库打包(scope为provided的除外),但是不打包Spring Boot的任何Launcher(启动器)。
  • NONE,将所有的依赖库打包,但是不打包Spring Boot的任何Launcher(启动器)。

为NONE时的MANIFEST.MF文件内容如下:

Manifest-Version: 1.0
Archiver-Version: Plexus Archiver
Built-By: beauty
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Spring-Boot-Version: 2.2.1.RELEASE
Created-By: Apache Maven 3.6.3
Build-Jdk: 1.8.0_251
Main-Class: com.demo.beauty.BeautyApplication

可以看到在layout为NONE时,打出的包中的org文件夹没有了,Manifest 文件中没有Start-Class属性,Main-Class属性值为项目的启动类。

common.jar之所以可以使用 java -jar 运行,和 MANIFEST.MF 文件里的配置关系密切

4.2 original jar包结构

image-1652144562722

可以看到通过mvn package构建的jar是一个普通的jar,包含的都是项目的字节文件和一些配置文件,没有将项目依赖的第三方jar包含进来。再看下MANIFEST.MF文件:

Manifest-Version: 1.0
Implementation-Title: java-common-utils
Implementation-Version: 0.0.1-SNAPSHOT
Build-Jdk-Spec: 1.8
Created-By: Maven Archiver 3.4.0

其中没有包含Start-Class、Main-Class等信息,这个与可执行jar的该文件存在很多差异,而且目录结构也有很大差异。

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

评论区