spring-boot-maven-plugin插件详解

一、 为什么Spring Boot项目自带这个插件

当我们在SpringBoot官方下载一个脚手架时,会发现pom.xml会自带spring-boot-maven-plugin插件

那为什么会自带这个插件呢?

我们知道Spring Boot项目,是可以通过java -jar 包名启动的

打包命令

bash 复制代码
mvn clean package

打包成功后就会生成一个jar包

发现是可以正常启动。

这个时候我们去掉spring-boot-maven-plugin插件,通过 mvn clean package 再打一次包。

打包依然成功,包名也一样,只是jar包大小有着天壤之别 发现只有3KB,之前的有17.7MB。我们通过java -jar 包名看能否启动成功 启动不了。

一般的maven项目的打包命令,不会把依赖的jar包也打包进去的,只是会放在jar包的同目录下,能够引用就可以了。
(使用maven 自带的 插件 maven-plugin-plugin 打包命令只会把自身的文件打入进去,并不会讲项目pom 依赖的包打进去。)
但是spring-boot-maven-plugin插件,会将依赖的jar包全部打包进去。该文件包含了所有的依赖和资源文件,可以直接在命令行或者传统的 Java Web 服务器上启动运行。

引入了spring-boot-maven-plugin插件后,在使用打包功能时会将mvn package 生成的 jar或者war 重新打包成可执行文件,同时修改原文件名,增加.origin 后缀。

二、插件介绍

1、插件标签详解

bash 复制代码
<!--使用的插件列表 。 -->
<plugins>
<!--plugin元素包含描述插件所需要的信息。 -->
<plugin>
	<!--插件在仓库里的group ID -->
	<groupId />
	<!--插件在仓库里的artifact ID -->
	<artifactId />
	<!--被使用的插件的版本(或版本范围) -->
	<version />
	<!--是否从该插件下载Maven扩展(例如打包和类型处理器),由于性能原因,只有在真需要下载时,该元素才被设置成enabled。 -->
	<extensions />
	<!--在构建生命周期中执行一组目标的配置。每个目标可能有不同的配置。 -->
	<executions>
		<!--execution元素包含了插件执行需要的信息 -->
		<execution>
			<!--执行目标的标识符,用于标识构建过程中的目标,或者匹配继承过程中需要合并的执行目标 -->
			<id />
			<!--绑定了目标的构建生命周期阶段,如果省略,目标会被绑定到源数据里配置的默认阶段 -->
			<phase />
			<!--配置的执行目标 -->
			<goals />
			<!--配置是否被传播到子POM -->
			<inherited />
			<!--作为DOM对象的配置 -->
			<configuration />
		</execution>
	</executions>
	<!--项目引入插件所需要的额外依赖 -->
	<dependencies>
		<!--参见dependencies/dependency元素 -->
		<dependency>
			......
		</dependency>
	</dependencies>
	<!--任何配置是否被传播到子项目 -->
	<inherited />
	<!--作为DOM对象的配置 -->
	<configuration />
</plugin>
</plugins>

spring-boot-maven-plugin:打包时排除provided依赖

spring-boot-maven-plugin 插件提供spring boot的maven打包支持。项目中scope为provided的依赖,比如 lombok、mybatis-plus 等,只作用于编译阶段,编译完成就可以功成身退了。在spring maven打包时,provided依赖会排除在包外,但springboot maven打包时,还会将这些依赖打进 war 包的 lib-provided 文件夹里或 jar 包的 lib 文件夹里。

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

以lombok为例,官方提供了以下方式可以将provided依赖从springboot项目包中排除。

1. 通过指定groupId和artifactId排除某个特定的依赖

bash 复制代码
<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>
  1. 指定groupId排除groupId相关的所有依赖
bash 复制代码
<build>
   <plugins>
       <plugin>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-maven-plugin</artifactId>
           <configuration>
               <excludeGroupIds>org.projectlombok</excludeGroupIds>
           </configuration>
       </plugin>
   </plugins>
</build>
  1. 按照配置文件排除依赖
bash 复制代码
    <profiles>
        <profile>
            <id>dev</id>
            <activation>
                <property>
                    <name>environment</name>
                    <value>dev</value>
                </property>
            </activation>
            <build>
                <plugins>
                    <plugin>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-maven-plugin</artifactId>
                        <version>2.7.12</version>
                        <executions>
                            <execution>
                                <goals>
                                    <goal>repackage</goal>
                                </goals>
                            </execution>
                        </executions>
                        <configuration>
                            <excludes>
                                <exclude>
                                    <groupId>org.projectlombok</groupId>
                                    <artifactId>lombok</artifactId>
                                </exclude>
                            </excludes>
                        </configuration>
                    </plugin>
                </plugins>
            </build>
        </profile>
        <profile>
            <id>prod</id>
            <activation>
                <property>
                    <name>environment</name>
                    <value>prod</value>
                </property>
            </activation>
            <build>
                <plugins>
                    <plugin>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-maven-plugin</artifactId>
                        <version>2.7.12</version>
                        <executions>
                            <execution>
                                <goals>
                                    <goal>repackage</goal>
                                </goals>
                            </execution>
                        </executions>
                        <configuration>
                        </configuration>
                    </plugin>
                </plugins>
            </build>
        </profile>
    </profiles>

mvn clean package -Pdev 使用dev配置文件去除lombok 依赖

2、内部goals

该插件提供了7个maven goal

build-image: 将程序使用 buildpack 打包进容器镜像中。

build-info:生成项目的构建信息文件 build-info.properties。根据当前 MavenProject 的内容生成一个 build-info.properties 文件

help:显示帮助信息。调用mvn spring-boot:help -Ddetail=true -Dgoal=以显示参数详细信息。

repackage:可生成可执行的jar包或war包。插件的核心goal。 默认的 goal,将普通 mvn package 打包成的 jar 重新打包成包含所有程序依赖项的可执行 jar/war 文件,并保留 mvn package 打包的 jar 为 .original 后缀

run:运行 Spring Boot 应用

start:在集成测试阶段,控制生命周期。通常用于集成测试方案中,在 mvn integration-test 阶段管理 Spring Boot 应用的生命周期。

stop:在集成测试阶段,控制生命周期。停止已通过 start 目标启动的应用程序。通常在 integration-test 完成后调用。

为什么这里插件名称叫spring-boot?

原因:自定义插件如果遵循 xxx-maven-plugin 的形式,maven 默认会将 maven-plugin 前面的内容作为插件前缀。

3.spring-boot-maven-plugin插件配置classifier

在Spring Boot项目中,如果你需要配置spring-boot-maven-plugin插件的classifier属性,通常是为了包含额外的构建输出,例如包含应用程序的源码或者文档。classifier可以让你构建出具有不同分类的构建产物。

以下是一个配置了classifier的spring-boot-maven-plugin的例子:

bash 复制代码
<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <version>2.6.3</version>
            <executions>
                <execution>
                    <goals>
                        <goal>repackage</goal>
                    </goals>
                    <configuration>
                        <classifier>exec</classifier>
                    </configuration>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

在这个例子中,classifier设置为exec,这将导致Maven在构建过程中生成一个附加的包,它将包含可执行的jar,但是它的文件名将带有exec作为分类器。例如,myapp-0.0.1-SNAPSHOT-exec.jar。

请注意,classifier的使用可能会影响依赖管理和项目的分发方式,因此在配置时需要小心考虑

三、该插件常见使用场景

1、排除provided类型的依赖

默认情况下,repackage命令所生成的包,会把项目中所有的依赖都打进去。

但其实在项目中scope为provided的依赖,比如 lombok、mybatis-plus等,只作用于编译阶段,编译完成就没用了。

若除去provided依赖,可以使用如下的配置:

bash 复制代码
<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>

2、指定Mainclass类

如果未指定,main将使用找到的第一个包含方法的编译类。

也就是说如果只有一个main方法,那不需要去指定,如果你这个模块存在两个主类,那么就需要在插件中指定具体哪个启动类了。

bash 复制代码
<plugin>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-maven-plugin</artifactId>
       <configuration>
                <mainClass>com.can.mavenplugin.MavenPluginApplication</mainClass>
                <layout>ZIP</layout>
        </configuration>
</plugin>

layout 属性用来指定打成 jar 还是war 文件,可用的值包括:ZIP 、JAR 、WAR、 NONE 。默认JAR

3、在使用spring-boot-maven-plugin的下生成普通的jar包

前面说了,如果使用该插件,那么打出来的包是一个可执行的jar包,这样的jar包,在被别的项目引用的时候,可能会出问题。

那如何打成一个普通的jar包。

当然你可以把这个插件注释掉,重新打包。不过这样做未必太愚蠢啦。

解决办法

第一种:通过命令

bash 复制代码
mvn clean package -D spring-boot.repackage.skip=true

加上-Dspring-boot.repackage.skip=true参数即可,此时只会生成一个普通的jar包

第二种:通过配置

bash 复制代码
<plugin>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-maven-plugin</artifactId>
	<configuration>
		<skip>true</skip>
	</configuration>
</plugin>

这两种方式打出来的包就是没有依赖jar的普通包了。

四、打出的jar 清单

可执行 Jar 包内部结构

将打出来的可执行 Jar 解压开我们能看到下面的结构:

可执行 jar 目录结构

bash 复制代码
├─BOOT-INF
│  ├─classes
│  └─lib
├─META-INF
│  ├─maven
│  ├─app.properties
│  ├─MANIFEST.MF      
└─org
    └─springframework
        └─boot
            └─loader
                ├─archive
                ├─data
                ├─jar
                └─util

结构对比

从上面的文件结构和 jar 清单内容来看,Spring Boot 打包后的 fatjar 对比 源 jar 主要有以下差异:

  • 源 jar 中主项目的 .class 文件被移至 fatjar 的 BOOT-INF/classes 文件夹下。
  • 新增 BOOT-INF/lib 文件夹,里面存放三方 jar 文件。
  • 新增 BOOT-INF/classpath.idx,用来记录 classpath 的加载顺序。
  • 新增 org/springframework/boot/loader 文件夹,这是 spring-boot-loader 编译后的 .class 文件。
  • 清单文件 MANIFEST.MF中新增以下属性:
  • Spring-Boot-Classpath-Index: 记录 classpath.idx 文件的地址。
  • Start-Class: 指定 Spring Boot 的启动类。
  • Spring-Boot-Classes: 记录主项目的 .class 文件存放路径。
  • Spring-Boot-Lib: 记录三方 jar 文件存放路径。
  • Spring-Boot-Version: 记录 Spring Boot 版本信息
  • Main-Class: 指定 jar 程序的入口类(可执行 jar 为 org.springframework.boot.loader.JarLauncher类)。
    我们先来重点关注两个地方:META-INF 下面的 Jar 包描述文件和 BOOT-INF 这个目录。
bash 复制代码
Manifest-Version: 1.0
Archiver-Version: Plexus Archiver
Built-By: xxxx
Start-Class: com.xxxx.AppServer
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Spring-Boot-Version: 2.1.6.RELEASE
Created-By: Apache Maven 3.3.9
Build-Jdk: 1.8.0_73
Main-Class: org.springframework.boot.loader.JarLauncher

在上面我们看到一个熟悉的配置Main-Class: org.springframework.boot.loader.JarLauncher。我们大概能猜到这个类是整个系统的入口。

再看下 BOOT-INF 这个目录下面,我们会发现里面是我们项目打出来的 class 文件和项目依赖的 Jar 包。看到这里,你可能已经猜到 Spring Boot 是怎么启动项目的了。

五 、Spring Boot 是怎么启动项目

JarLauncher

bash 复制代码
public class JarLauncher extends ExecutableArchiveLauncher {

	static final String BOOT_INF_CLASSES = "BOOT-INF/classes/";

	static final String BOOT_INF_LIB = "BOOT-INF/lib/";

	public JarLauncher() {
	}

	protected JarLauncher(Archive archive) {
		super(archive);
	}

	@Override
	protected boolean isNestedArchive(Archive.Entry entry) {
		if (entry.isDirectory()) {
			return entry.getName().equals(BOOT_INF_CLASSES);
		}
		return entry.getName().startsWith(BOOT_INF_LIB);
	}

	public static void main(String[] args) throws Exception {
        //项目入口,重点在launch这个方法中
		new JarLauncher().launch(args);
	}
}
bash 复制代码
//launch方法
protected void launch(String[] args) throws Exception {
    JarFile.registerUrlProtocolHandler();
    //创建LaunchedURLClassLoader。如果根类加载器和扩展类加载器没有加载到某个类的话,就会通过LaunchedURLClassLoader这个加载器来加载类。这个加载器会从Boot-INF下面的class目录和lib目录下加载类。
    ClassLoader classLoader = createClassLoader(getClassPathArchives());
    //这个方法会读取jar描述文件中的Start-Class属性,然后通过反射调用到这个类的main方法。
    launch(args, getMainClass(), classLoader);
}  

简单总结

Spring Boot 可执行 Jar 包的入口点是 JarLauncher 的 main 方法;

这个方法的执行逻辑是先创建一个 LaunchedURLClassLoader,这个加载器加载类的逻辑是:

先判断根类加载器和扩展类加载器能否加载到某个类,如果都加载不到就从 Boot-INF 下面的 class 和 lib 目录下去加载;

读取Start-Class属性,通过反射机制调用启动类的 main 方法,这样就顺利调用到我们开发的 Spring Boot 主启动类的 main 方法了。

六 SpringBoot 工程部署的 jar 包瘦身

通过上方分析 fat jar下 有 classes,lib 两个文件夹,我们编译好的代码是放在 classes 里面的,而我们所依赖的 jar 包都是放在 lib 文件夹下

classes 部分是非常小的(我的是160kb左右),lib部分是非常大的(我的是70M左右),所以上传很慢

那我们可以将我们自己写的代码部分与所依赖的 maven jar 包部分拆开上传,每次只需要上传我们自己写的代码部分即可

正常打包

首先,我们项目的 pom.xml 文件中的打包方式如下:

bash 复制代码
 		<plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>

这是 SpringBoot 中默认的打包方式,我们先按照这种方式打包出来,得到一个 jar 包,我们将 jar 包解压,如果不能直接解压,则将后缀改为 zip 再进行解压,我们只需要拿到 BOOT-INF 中的 lib 目录即可

改变打包方式

我们对 SpringBoot 中默认的打包方式做一些配置

bash 复制代码
<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>
                <mainClass>com.Application</mainClass>
                <layout>ZIP</layout>
                <includes>
                    <include>
                        <groupId>nothing</groupId>
                        <artifactId>nothing</artifactId>
                    </include>
                </includes>
            </configuration>
            <executions>
                <execution>
                    <goals>
                        <goal>repackage</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>
  • mainClass,我们指定了项目的启动类
  • layout,我们指定了打包方式为 ZIP,注意:一定是大写的
  • includes,有自己的依赖 jar,可以在此导入
  • repackage,剔除其它的依赖,只需要保留最简单的结构
    再次打包
    我们再次点击 maven package,得到一个 jar 包,可以看到此时的 jar 包只有167kb了(原先70M+)

上传启动

我们将 lib 目录,以及最后打包的瘦身项目 jar 包,上传至服务器同一目录

使用命令

bash 复制代码
nohup java -Dloader.path=./lib -jar ./sbm-0.0.1-SNAPSHOT.jar &

-Dloader.path,告诉它所依赖的 maven jar 包位置

admin4j-example-1.0.jar,项目 jar 包的名字

nohup、&,使得 jar 包在服务后台运行

总结

使用瘦身部署,方便每次的迭代更新,不用每次都上传一个很大的 jar 包,从而节省部署时间。但是每次依赖jar包版本或者有新增了,需要更改下lib目录数据

相关推荐
zjjuejin1 天前
Maven 云原生时代面临的八大挑战
java·后端·maven
摆烂且佛系2 天前
IDEA Maven 仓库配置优先级
github·maven·intellij-idea
momo_via2 天前
maven下载与安装及在IDEA中配置maven
java·maven·intellij-idea
李贺梖梖2 天前
Maven 设置项目编码,防止编译打包出现编码错误
java·maven
洛克大航海2 天前
Ubuntu安装JDK与Maven和IntelliJ IDEA
ubuntu·jdk·maven·intellij idea
假客套2 天前
2025 FastExcel在Java的Maven项目的导出和导入,简单易上手,以下为完整示例
java·maven·fastexcel
有梦想的攻城狮2 天前
Maven中的settings.xml文件配置详解
xml·java·maven·settings.xml
李贺梖梖3 天前
maven本地仓库有相应的依赖,依旧会从远程仓库拉取问题的原因及解决
maven
纳于大麓3 天前
Android Maven私服搭建(Windows)
java·maven
诸神缄默不语4 天前
Maven用户设置文件(settings.xml)配置指南
xml·java·maven