许多公司都会使用springboot作为服务应用开发框架,springboot框架提供了一套自己的打包机制,是通过 spring-boot-maven-plugin
插件来实现的。使用该 maven 插件打包时,将应用程序及其依赖jar一起打包到一个独立的jar包/war包中,打出来的是fat jar,可以直接启动。
- 打包时会动态生成jar的启动类
org.springframework.boot.loader.JarLauncher
,借助该类对springboot应用程序进行启动。 - 官方文档地址:https://docs.spring.io/spring-boot/docs/2.2.1.RELEASE/maven-plugin/
- 应用示例:SpringBoot maven项目打jar包并部署到 linux/windows服务
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
插件:
- 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进行查看,如下图:
如果需要设置其他属性,需要在当前应用的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俩命令:
执行以上命令时会自动触发spring-boot-maven-plugin插件的repackage目标,完后可以在target目录下看到生成的jar,如下图:
可以看到生成了两个jar相关的文件,其中 common.jar
是 spring-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>
效果如下:
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,为原始包,如下图。
对比下项目模块target目录下输出的jar包,为可执行jar包,可以看到比本地仓库中的体积大了很多,本例为28544KB,如下图所示:
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中实践
-
在pom中按上述代码配置jvmArguments参数。
-
添加一个远程调试配置:
- Host为 localhost,Port 应与spring-boot-maven-plugin中配置的端口一致,这里是5005
- 执行spring-boot-maven-plugin的
run goal
这时可以看到,进程在监听5005端口,等待调试器连接
- DEBUG模式下启动远程调试,发现已连接上5005端口
- 可以打断点调试了
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目录结构
其中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包结构
可以看到通过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>
评论区