1. 简介

Apache Maven 是一个广泛使用的项目依赖管理和构建工具。

近年来,Spring Boot 成为了构建应用程序的热门框架。为了支持在 Maven 中使用 Spring Boot,官方提供了 Spring Boot Maven Plugin 插件。

我们知道,使用 Maven 打包应用为 JAR 或 WAR 包时,通常会使用 mvn package 命令。但 Spring Boot Maven Plugin 提供了一个额外的目标(goal)叫做 repackage,也可以通过 mvn 命令调用。

有时候这两个命令容易混淆。本文将详细讲解 mvn packagespring-boot:repackage 的区别。

2. 示例 Spring Boot 应用

我们先创建一个简单的 Spring Boot 应用作为示例:

@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

为了验证应用是否正常运行,我们添加一个简单的 REST 接口:

@RestController
public class DemoRestController {
    @GetMapping(value = "/welcome")
    public ResponseEntity<String> welcomeEndpoint() {
        return ResponseEntity.ok("Welcome to Baeldung Spring Boot Demo!");
    }
}

3. Maven 的 package 目标

构建 Spring Boot 应用只需要引入 spring-boot-starter-web 依赖即可:

<artifactId>spring-boot-artifacts-2</artifactId>
<packaging>jar</packaging>
...
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
...

Maven 的 package 目标会把编译后的代码打包成可分发的格式,在这里是 JAR 格式

$ mvn package
[INFO] Scanning for projects...
[INFO] ------< com.baeldung.spring-boot-modules:spring-boot-artifacts-2 >------
[INFO] Building spring-boot-artifacts-2 1.0.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
 ... 
[INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ spring-boot-artifacts-2 ---
[INFO] Building jar: /home/kent ... /target/spring-boot-artifacts-2.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
 ...

执行完 mvn package 后,我们可以在 target 目录下找到生成的 JAR 文件:spring-boot-artifacts-2.jar。我们来看看它的内容:

$ jar tf target/spring-boot-artifacts-2.jar
META-INF/
META-INF/MANIFEST.MF
com/
com/baeldung/
com/baeldung/demo/
application.yml
com/baeldung/demo/DemoApplication.class
com/baeldung/demo/DemoRestController.class
META-INF/maven/...

⚠️ 可以看到,这个 JAR 文件只包含了项目源码中的资源和编译后的 Java 类,并没有包含运行时所需的依赖项。

❌ 因此,即使这是一个 Spring Boot 应用,也无法直接通过 java -jar JAR_FILE 来运行。因为缺少了如嵌入式 Tomcat 这类运行环境。

如果要让这个 JAR 可执行,我们需要构建一个 fat JAR,Spring Boot Maven Plugin 正好可以帮我们做到这一点。

4. Spring Boot Maven Plugin 的 repackage 目标

下面我们就来了解 spring-boot:repackage 到底做了什么。

4.1. 添加 Spring Boot Maven Plugin

要使用 repackage 目标,我们需要在 pom.xml 中添加插件配置:

<build>
    <finalName>${project.artifactId}</finalName>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

4.2. 执行 spring-boot:repackage

现在尝试执行 spring-boot:repackage

$ mvn clean spring-boot:repackage     
 ...
[INFO] --- spring-boot-maven-plugin:2.3.3.RELEASE:repackage (default-cli) @ spring-boot-artifacts-2 ---
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
...
[ERROR] Failed to execute goal org.springframework.boot:spring-boot-maven-plugin:2.3.3.RELEASE:repackage (default-cli) 
on project spring-boot-artifacts-2: Execution default-cli of goal 
org.springframework.boot:spring-boot-maven-plugin:2.3.3.RELEASE:repackage failed: Source file must not be null -> [Help 1]
...

❌ 报错了!原因很简单:spring-boot:repackage 需要有一个已经存在的 JAR/WAR 包作为输入源,然后将所有运行时依赖一起打包进最终的 artifact 中,从而生成一个可执行的 fat JAR。

所以我们需要先执行一次 package 再执行 repackage

$ mvn clean package spring-boot:repackage
 ...
[INFO] Building spring-boot-artifacts-2 1.0.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
 ...
[INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ spring-boot-artifacts-2 ---
[INFO] Building jar: /home/kent/.../target/spring-boot-artifacts-2.jar
[INFO] 
[INFO] --- spring-boot-maven-plugin:2.3.3.RELEASE:repackage (default-cli) @ spring-boot-artifacts-2 ---
[INFO] Replacing main artifact with repackaged archive
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
 ...

✅ 构建成功!

4.3. 重新打包后的 JAR 内容

查看 target 目录,可以看到两个文件:

$ ls -1 target/*jar*
target/spring-boot-artifacts-2.jar
target/spring-boot-artifacts-2.jar.original

其中 .original 是原始的 mvn package 产物,而 spring-boot-artifacts-2.jar 是经过 repackage 处理后的 fat JAR。

我们来看一下它的内容:

$ jar tf target/spring-boot-artifacts-2.jar 
META-INF/
META-INF/MANIFEST.MF
 ...
org/springframework/boot/loader/JarLauncher.class
 ...
BOOT-INF/classes/com/baeldung/demo/
BOOT-INF/classes/application.yml
BOOT-INF/classes/com/baeldung/demo/DemoApplication.class
BOOT-INF/classes/com/baeldung/demo/DemoRestController.class
META-INF/maven/com.baeldung.spring-boot-modules/spring-boot-artifacts-2/pom.xml
META-INF/maven/com.baeldung.spring-boot-modules/spring-boot-artifacts-2/pom.properties
BOOT-INF/lib/
BOOT-INF/lib/spring-boot-starter-web-2.3.3.RELEASE.jar
...
BOOT-INF/lib/spring-boot-starter-tomcat-2.3.3.RELEASE.jar
BOOT-INF/lib/tomcat-embed-core-9.0.37.jar
BOOT-INF/lib/jakarta.el-3.0.3.jar
BOOT-INF/lib/tomcat-embed-websocket-9.0.37.jar
BOOT-INF/lib/spring-web-5.2.8.RELEASE.jar
...
BOOT-INF/lib/httpclient-4.5.12.jar
...

✅ 很明显,这个 fat JAR 不仅包含了我们的类,还包含了所有运行时依赖库(比如嵌入式的 Tomcat),这样就可以直接运行了。

启动应用试试看:

$ java -jar target/spring-boot-artifacts-2.jar 
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/

2020-12-22 23:36:32.704  INFO 115154 [main] com.baeldung.demo.DemoApplication      : Starting DemoApplication on YK-Arch with PID 11515...
...
2020-12-22 23:36:34.070  INFO 115154 [main] o.s.b.w.embedded.tomcat.TomcatWebServer: Tomcat started on port(s): 8080 (http) ...
2020-12-22 23:36:34.078  INFO 115154 [main] com.baeldung.demo.DemoApplication      : Started DemoApplication in 1.766 seconds ...

再测试一下接口:

$ curl http://localhost:8080/welcome
Welcome to Baeldung Spring Boot Demo!

✅ 成功响应,说明应用运行正常。

4.4. 在 Maven 生命周期中自动执行 repackage

我们可以把 repackage 配置到 Maven 的 package 阶段中,这样每次执行 mvn package 时都会自动完成 repackage 操作。

配置如下:

<build>
    <finalName>${project.artifactId}</finalName>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <executions>
                <execution>
                    <goals>
                        <goal>repackage</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

然后运行:

$ mvn clean package
 ...
[INFO] Building spring-boot-artifacts-2 1.0.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
...
[INFO] --- spring-boot-maven-plugin:2.3.3.RELEASE:repackage (default) @ spring-boot-artifacts-2 ---
[INFO] Replacing main artifact with repackaged archive
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
 ...

再次查看文件系统:

$ ls -lh target/*jar*
-rw-r--r-- 1 kent kent  29M Dec 22 23:56 target/spring-boot-artifacts-2.jar
-rw-r--r-- 1 kent kent 3.6K Dec 22 23:56 target/spring-boot-artifacts-2.jar.original

✅ fat JAR 已经生成,完美集成进 Maven 构建流程。

5. 总结

本文对比了 mvn packagespring-boot:repackage 的区别:

  • mvn package 只是普通的打包,不包含依赖。
  • spring-boot:repackage 会基于已有包重新打包,生成包含所有依赖的 fat JAR,可以直接运行。
  • 可以通过 <executions>repackage 绑定到 package 生命周期中,实现自动化构建。

完整代码可以在 GitHub 上找到。


原始标题:Difference Between spring-boot:repackage and Maven package | Baeldung