Spring Boot项目在构建时最常用的两种产物格式是可执行JAR和WAR包,二者虽然都能承载应用代码和依赖,但内部结构、启动方式以及部署模型存在本质区别。默认情况下,Spring Boot通过spring-boot-maven-plugin的repackage目标生成可执行JAR,应用直接以java -jar方式运行,内嵌Tomcat或Jetty让服务自包含。而如果将打包类型切换为war,应用则可以被部署到外部的Servlet容器中,由容器管理生命周期和资源。

理解这两种打包方式的关键在于它们的目录布局和类加载机制。可执行JAR会把项目编译后的class文件放在BOOT-INF/classes目录下,把第三方依赖以嵌套JAR的形式放在BOOT-INF/lib中,同时引入Spring Boot自定义的加载器类。WAR包则遵循Servlet规范,将class文件放入WEB-INF/classes,依赖放入WEB-INF/lib,两者在文件组织上就已经分化。
一、可执行JAR的打包原理与启动机制
标准的Java JAR包通过META-INF/MANIFEST.MF文件中的Main-Class指定程序入口,JVM会根据这一字段调用对应的main方法。但普通JAR的类路径只包含自身以及外部通过-cp参数传入的依赖,无法直接加载嵌套在JAR内部的JAR文件。Spring Boot的可执行JAR正是为了解决这一问题而设计的,它在清单文件中额外加入了Start-Class字段,用来指向真正包含main方法的启动类,而Main-Class则固定指向org.springframework.boot.loader.JarLauncher。
当执行java -jar app.jar时,JVM首先调用JarLauncher的main方法。JarLauncher会创建LaunchedURLClassLoader,这个类加载器能够解析BOOT-INF/lib目录下的嵌套JAR,并把它们作为URL添加到类路径中。随后通过反射调用Start-Class指定类中的main方法,从而进入SpringApplication的启动流程。这个过程的清单文件内容大致如下:
Manifest-Version: 1.0 Main-Class: org.springframework.boot.loader.JarLauncher Start-Class: com.example.demo.DemoApplication Spring-Boot-Version: 3.2.5 Spring-Boot-Classes: BOOT-INF/classes/ Spring-Boot-Lib: BOOT-INF/lib/
从目录结构上看,可执行JAR之所以能独立运行,是因为它在归档内同时携带了运行时所需的全部依赖和内嵌容器。开发者不需要在服务器上预装Tomcat,也不用手工管理类路径,部署动作被简化为上传一个文件并执行一条命令。这种自包含特性在微服务架构和容器化场景中非常受欢迎,Docker镜像构建时只需要把JAR复制进去并指定启动命令即可。
在Maven项目中,生成可执行JAR的配置通常依赖spring-boot-maven-plugin。该插件在package阶段执行repackage目标,对原始JAR进行二次封装,将依赖和加载器写入归档。典型配置如下:
<build>
<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>
如果没有执行repackage目标,Maven生成的只是普通JAR,清单文件中不会包含JarLauncher相关配置,运行时会直接报错提示没有主清单属性。这一点在实际项目中经常遇到,开发者在排查启动问题时可以先检查JAR内部结构,确认是否包含BOOT-INF目录和Spring Boot加载器类。
二、WAR包的生成方式与外部容器部署
要生成传统WAR包,需要在pom.xml中把packaging改为war,同时将内嵌Tomcat的依赖范围设置为provided。这样在编译和测试阶段仍然可以使用内嵌容器,但打包时不会把Tomcat相关类打进WAR包,避免与外部容器中的Servlet API产生冲突。常见的配置方式如下:
<packaging>war</packaging>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
除了依赖调整,启动类还需要继承SpringBootServletInitializer并重写configure方法。这个步骤的作用是让应用在外部Servlet容器中启动时,能够把Spring Boot的ApplicationContext和Servlet容器的生命周期进行桥接。代码如下:
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
public class DemoApplication extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(DemoApplication.class);
}
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
当WAR包被放入外部Tomcat的webapps目录后,Tomcat会解压归档并按照Servlet规范加载应用。此时不会执行main方法,而是由SpringBootServletInitializer在onStartup回调中创建根ApplicationContext。WAR包的目录结构也在打包阶段做了调整,应用类位于WEB-INF/classes,依赖位于WEB-INF/lib,同时可能生成WEB-INF/lib-provided目录来存放provided范围的依赖,方便排查问题。
WAR部署的一个典型优势是对JSP视图的支持。Spring Boot内嵌Tomcat对JSP的兼容性有限,尤其是在打成可执行JAR时,JSP文件无法从嵌套JAR中解析。如果项目大量使用JSP页面,部署到外部Servlet容器通常是更省心的选择。此外,有些企业的运维体系已经围绕外部Tomcat建立了监控、日志收集和热部署机制,沿用WAR包部署可以减少基础设施的改造。
三、JAR与WAR的选型对比及混合模式
选择可执行JAR还是WAR包,核心取决于部署环境和团队对基础设施的控制能力。可执行JAR把容器打包进应用,部署简单、一致性强,特别适合微服务、云原生和DevOps流水线。WAR包则把容器交给运维团队管理,应用更轻量,适合有统一Servlet容器规划的传统企业环境。下表从几个关键维度做了对比:
| 对比维度 | 可执行JAR | WAR包 |
|---|---|---|
| 启动方式 | java -jar直接启动,内嵌容器 | 由外部Tomcat等容器加载并启动 |
| 依赖管理 | 全部依赖打入BOOT-INF/lib | provided范围依赖不打包,运行时由容器提供 |
| 部署复杂度 | 单文件上传,命令启动 | 需要预装容器,上传WAR后自动部署 |
| JSP支持 | 有限,不建议使用JSP | 天然支持,适合JSP视图项目 |
| 运维集成 | 与容器化、编排工具结合紧密 | 与传统Tomcat监控、热部署体系契合 |
实际工作中还有一种混合模式值得注意:Spring Boot允许生成可执行WAR。也就是说,WAR包既可以被直接部署到外部Servlet容器,也可以使用java -jar方式运行。它的原理是在WAR包中同时保留了WEB-INF目录和Spring Boot加载器相关结构,并通过Main-Class指向JarLauncher。这样在开发阶段可以用命令快速启动,生产环境又能无缝交给外部容器管理。要达到这一效果,需要保留spring-boot-maven-plugin的repackage配置,同时将tomcat依赖设置为provided,并继承SpringBootServletInitializer。
不过在混合模式下,应用会面临两套启动路径,排查问题时需要先明确实际触发的是内嵌容器路径还是外部容器路径。如果应用部署到Tomcat后访问正常,但用java -jar启动时报找不到Servlet容器相关类,通常是因为provided依赖没有在可执行归档中保留运行所需的完整容器实现,此时需要检查打包配置是否与预期一致。
综合来看,新启动的微服务项目如果没有特殊JSP需求,优先选择可执行JAR会获得更低的部署成本和更好的容器化体验。如果项目必须使用JSP,或者企业已经存在成熟的Servlet容器运维平台,那么WAR包仍然是不能忽视的选项。理解两种格式背后的加载机制和目录差异,能帮助开发者在遇到启动异常、依赖冲突或部署问题时更快定位根因,而不是盲目切换打包方式。
Spring Boot打包可执行JARWAR部署修改时间:2026-10-04 19:17:28