正确打包 Spring Boot 到 war

本文撰写时的 Spring 版本: Spring-Boot 2.0.5.RELEASE | gradle 4.10.2

众所周知, Spring Boot 在开发时之所以能直接启动, 是因为内置了 tomcat . 同时这也使得 Spring Boot 可以直接输出为可执行的 jar 文件.

那么问题来了, 如果我们需要将应用打包为 war 文件并部署到外部的 tomcat 服务器怎么办.

在 Google 搜索这个问题, 就会看到很多人跟你说, 在 gradle 里, 把 tomcat 的依赖 exclude 掉就好了. 但是这样的话, 本地调试就没法直接启动了, 既然 Spring Boot 的设计是完美的, 所以肯定不是这么弄的.

于是我们找到了 Spring Boot 文档 Spring Boot Gradle Plugin Reference Guide

我们要用 providedRuntime 来标记 tomcat 的依赖, 这有什么用待会再说.

但是我们马上会发现, gradle 找不到符号 providedRuntime .

可能是文档有遗漏, 实际上我们必须先启用 war 插件(在此之前应该已经使用了 spring-boot-gradle-plugin )

apply plugin: 'war'

然后我们的依赖就变为这样

dependencies {
    // https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web
    compile group: 'org.springframework.boot', name: 'spring-boot-starter-web'
    // https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-tomcat
    providedRuntime group: 'org.springframework.boot', name: 'spring-boot-starter-tomcat'
}

providedRuntime 并不会将依赖从 classpath 去除, 所以我们本地开发时依然可以直接启动.

然后我们尝试将应用打包为 war

./gradlew bootWar

注意, 执行的 gradle task 为 bootWar , 默认的 war 会被 spring-boot-gradle-plugin 跳过.

我们来看一下打包得到的 war 文件的内部结构

META-INF
    MANIFEST.MF
org
    springframework
        boot
            loader
                (Launcher)
WEB-INF
    classes
        (user code)
    lib
        (third party lib)
    lib-provided
        (tomcat)

其中的 MANIFEST.MF 与普通 jar 是一样的, 也就是说, 这个 war 可以被当做 jar 来执行, 这种 war 叫做 executable war

java -jar application-name.war

executable war 启动时, 实际上的入口类是 MANIFEST.MF 中记录的 Spring Boot Loader 类.

之后 lib-provided 目录也会被其动态加载, 所以可以正常运行.

而这个 executable war 同时也确实是一个合法的 war , 可以被外部的 tomcat 正确加载.

因此, 无论是在开发中直接启动, 还是输出为 jarwar 并当做普通 java 程序来运行, 还是输出为 war 并由外部 tomcat 加载, 都是正常的.


原文:正确打包 Spring Boot 到 war · HonKit