打包:Jar 与 War
本教程共 48 篇 · 第 42 篇 · 更新于 2026-08-13 · 约 3 分钟阅读
本节目标:理解可执行 Jar 的原理和内部结构,会用 Maven 打包并用
java -jar运行,掌握 War 包部署到外部 Tomcat 的方法。
一个命令,从代码到制品
开发时用 mvn spring-boot:run,代码直接跑。上线不一样:需要把整个应用打成一个文件,拷到服务器上就能运行。
mvn clean package
执行完,target/ 目录下出现 demo-0.0.1-SNAPSHOT.jar。运行它:
java -jar target/demo-0.0.1-SNAPSHOT.jar
应用启动,Spring Boot 的 Banner 打出来,服务就起来了。这个 Jar 为什么能直接跑?普通 Jar 可没这个本事。
普通 Jar 与可执行 Jar
普通 Jar 只是 class 文件的压缩包,java -jar 能跑的前提是:META-INF/MANIFEST.MF 里有 Main-Class,且依赖的类都在。Spring Boot 应用依赖几十上百个第三方库,普通 Jar 装不下它们。
Spring Boot 的解法:repackage。spring-boot-maven-plugin 在 package 阶段把普通 Jar 重新打包:
- 应用自己的 class 放进
BOOT-INF/classes/。 - 所有依赖 Jar 原样塞进
BOOT-INF/lib/,一个都不丢。 - 清单文件指定启动器:
Main-Class: org.springframework.boot.loader.launch.JarLauncher。 - 真正的入口写在
Start-Class里,指向你的主类。
所以这个 Jar 叫 Boot-Jar,或者「胖 Jar」(fat jar)。用解压工具打开看看:
demo-0.0.1-SNAPSHOT.jar
├── META-INF/
│ └── MANIFEST.MF # Main-Class 与 Start-Class
├── BOOT-INF/
│ ├── classes/ # 你的代码和资源文件
│ │ └── com/example/demo/...
│ └── lib/ # 所有第三方依赖
│ ├── spring-boot-4.1.0.jar
│ └── spring-web-7.0.0.jar
└── org/springframework/boot/loader/
└── launch/JarLauncher.class # 启动器
JarLauncher 负责从嵌套的 BOOT-INF/lib 里加载依赖,再调用 Start-Class 指向的主类。这就是「一个 Jar 到处跑」的真相。
Note4.x 的启动器包名是
org.springframework.boot.loader.launch,3.1 之前是org.springframework.boot.loader。看到旧文章写的JarLauncher路径不一致,是版本差异。
打包配置在哪
spring-boot-maven-plugin 在初始项目里就配好了:
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
repackage 目标默认绑定在 package 阶段,不用额外配置。注意:只有这个插件在,Jar 才是可执行的;去掉它,打出来的是普通 Jar。
运行时的常用姿势
带参数启动:外部的配置优先级最高,直接命令行覆盖:
java -jar demo.jar --server.port=9090
指定 Profile:
java -jar demo.jar --spring.profiles.active=prod
后台运行(Linux):
nohup java -jar demo.jar > app.log 2>&1 &
jarmode:拆开胖 Jar
胖 Jar 运行时要从嵌套 Jar 里加载类,有一点点开销。生产环境常常先解压再跑,启动更快。
Spring Boot 内置了 tools 模式,专门干这个:
java -Djarmode=tools -jar demo.jar list-layers
java -Djarmode=tools -jar demo.jar extract --destination application
extract 把 Jar 解压成目录结构,lib/ 文件夹放依赖,应用 class 单独放。解压后这样运行:
java -jar application/demo.jar
解压形态还支持 AOT 缓存和 CDS(类数据共享),能进一步压缩启动时间。这个在第 45 章展开。
War:回到传统 Servlet 容器
大多数场景 Jar 就够了。但有些公司要求部署到外部 Tomcat,和其他 Web 应用共用一个服务器。这时要打 War 包。
第一步:改打包方式
<packaging>war</packaging>
第二步:主类继承 SpringBootServletInitializer
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
@SpringBootApplication
public class DemoApplication extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(DemoApplication.class);
}
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
SpringBootServletInitializer 是给外部容器用的入口。Tomcat 启动时找到它,由它来装配 Spring 应用。main 方法保留,本地还能用 java -jar 跑。
第三步:Tomcat 依赖改为 provided
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat-runtime</artifactId>
<scope>provided</scope>
</dependency>
provided 意思是:打包时不要把 Tomcat 塞进去,服务器上已经有 Tomcat 了。不改成 provided,War 里带着内嵌 Tomcat,和外部容器冲突。
Warning打了 War 之后,内嵌服务器只在
java -jar运行时生效;部署到外部 Tomcat 时,由 Tomcat 负责 HTTP 服务,server.port配置不再起作用,要改端口去改 Tomcat 的server.xml。
第四步:部署
mvn clean package
把 target/demo.war 拷到 Tomcat 的 webapps/ 目录,启动 Tomcat。访问路径是 http://localhost:8080/demo/,多了一段上下文路径,默认是 War 文件名。
Jar 还是 War:怎么选
- 新项目、微服务、容器化:Jar,简单直接,
java -jar一条命令。 - 老机房、共用 Tomcat:War,迁就现有运维体系。
- 拿不准就 Jar。Spring Boot 的设计主线就是可执行 Jar,War 是兼容传统部署的选项。
小结
- 可执行 Jar = 应用 class + 全部依赖 + 启动器,
java -jar直接跑。 - Boot-Jar 结构:
BOOT-INF/classes放代码,BOOT-INF/lib放依赖。 - 生产环境可先
jarmode extract解压再运行。 - War 部署外部 Tomcat:改 packaging、继承
SpringBootServletInitializer、Tomcat 依赖改provided。