Spring Boot 与传统 Spring:从 WAR 到可执行 JAR,颠覆性的部署哲学

引言:一个常见的困惑

如果你是从传统的 Spring MVC 项目过渡到 Spring Boot 的开发者,很可能会有这样一个疑问:为什么以前我们总是打一个 WAR 包丢到 Tomcat 的 webapps 目录下,而现在 Spring Boot 项目直接打成一个 JAR 包,用 java -jar 命令就能启动一个 Web 应用?

这个看似简单的打包方式变化的背后,是 Spring Boot "约定大于配置""自包含" 设计理念的深刻体现。本文将深入探讨 Spring Boot 与 Tomcat 的关系,并解析这一变革为我们带来的便利与思考。


一、传统模式:WAR 包与外部 Tomcat 的"租客-酒店"关系

在 Spring Boot 诞生之前,Java Web 应用的部署方式几乎是统一的。

1.1 如何工作?

  1. 开发阶段 :我们在项目中编写 web.xml、Spring 配置文件,配置 DispatcherServlet、ContextLoaderListener 等。
  2. 打包阶段 :使用 Maven 或 Gradle,将项目及其依赖(除了 Servlet API 这类 provided 依赖)打包成一个 WAR (Web Application Archive) 文件。
  3. 部署阶段 :我们需要在服务器上预先安装并启动一个 Tomcat 实例 。然后将 WAR 文件复制到 Tomcat 的 webapps 目录下。
  4. 运行阶段 :Tomcat 监控到新的 WAR 文件,会解压它,加载其中的类和相关库,并根据 web.xml 的配置来启动应用。

1.2 关系比喻:酒店与租客

  • Tomcat :像一个功能齐全的酒店。它独立运行,管理着基础设施(端口 8080、线程池、JNDI 资源、生命周期管理等)。
  • WAR 应用 :像一个租客。它"入住"酒店,享受酒店提供的服务,但必须遵守酒店的规则(如指定的 Servlet 版本)。

1.3 这种模式的优缺点

  • 优点
    • 资源共享:一个 Tomcat 可以部署多个应用,节省资源。
    • 运维统一:运维人员可以集中管理一个 Tomcat 实例,进行监控、调优和打补丁。
  • 缺点
    • 环境依赖性强:应用的行为严重依赖外部 Tomcat 的版本和配置。"在我这儿跑得好好的,怎么到你那儿就不行了?"是常见问题。
    • 部署繁琐:需要先配置环境,再部署应用,步骤较多。
    • 移植性差:难以实现完美的"一次构建,到处运行"。

二、Spring Boot 模式:可执行 JAR 与嵌入式容器的"机甲-驾驶员"关系

Spring Boot 引入了一种全新的思路:为什么不把服务器(Tomcat)和应用打包在一起呢?

2.1 如何工作?

  1. 开发阶段 :我们引入 spring-boot-starter-web 依赖。这个 Starter 已经默默地帮我们引入了嵌入式 Tomcat 的依赖。

  2. 打包阶段 :Spring Boot 的 Maven/Gradle 插件会将所有依赖(包括嵌入式 Tomcat) 以及项目代码一起打包成一个可执行的、胖乎乎的(Fat)JAR 包。这个 JAR 包内有特殊的结构,知道如何启动自己。

  3. 部署与运行阶段 :我们不需要安装 Tomcat。只需要在目标机器上安装 JRE,然后执行一条简单的命令:

    bash 复制代码
    java -jar my-spring-boot-app-0.0.1.jar

    这条命令会启动应用内的 main 方法(通常是 SpringApplication.run()),该方法会从内部实例化并启动一个 Tomcat 实例,并将当前应用部署上去。

2.2 关系比喻:机甲与驾驶员

  • 可执行 JAR :是一个配备了机甲的驾驶员
    • 驾驶员:是你的业务逻辑代码。
    • 机甲 :就是内嵌的 Tomcat,它为驾驶员提供了强大的战斗(Web 服务)能力。
      两者紧密结合,形成一个独立、完整、可随时投入战斗的个体。

2.3 嵌入式容器的魔法

Spring Boot 的嵌入式支持不仅限于 Tomcat。通过简单的排除和引入依赖,你可以轻松切换容器:

xml 复制代码
<!-- 默认使用 Tomcat -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- 切换至 Jetty -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jetty</artifactId>
</dependency>

<!-- 切换至 Undertow -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-undertow</artifactId>
</dependency>

这种灵活性是传统部署模式难以企及的。

2.4 这种模式的优缺点

  • 优点
    • 开发体验极佳:开箱即用,无需关心服务器配置。
    • 部署极其简单:只需要有 Java 环境,一行命令即可运行。完美契合云原生和容器化(Docker)的理念。
    • 环境一致性:应用在任何地方运行的表现都是一致的,彻底解决了环境差异问题。
    • 高度可定制 :通过 application.properties 即可轻松配置容器参数(端口、SSL、连接池等),无需触碰 server.xml
  • 缺点
    • 资源占用:每个应用都自带一个服务器,如果机器上部署了多个应用,可能会有多份 Tomcat 开销。
    • 传统运维不适:传统的运维团队可能需要转变观念,从管理几个大的 Tomcat 实例转变为管理大量独立的应用进程。

三、对比总结:一张图看清差异

特性维度 Spring Boot (可执行 JAR) 传统 Spring (WAR)
容器关系 嵌入式 (Embedded),与应用一体 外部 (External),与应用分离
打包格式 可执行 Fat JAR WAR
启动命令 java -jar app.jar $TOMCAT_HOME/bin/startup.sh
环境依赖 仅需 JRE 需预装匹配版本的 Tomcat/JEE 服务器
容器定制 应用配置文件 (application.yml) 服务器配置文件 (server.xml)
应用隔离性 (进程级别隔离) (共享容器,可能相互影响)
适用架构 微服务、云原生、分布式系统 传统单体应用、企业级应用服务器环境

四、拓展:Spring Boot 也能打 WAR 包?

值得注意的是,Spring Boot 也提供了打 WAR 包的能力。这通常是为了向旧世界妥协,比如部署到公司强制要求使用的 WebLogic、WebSphere 或现有的 Tomcat 集群。

当你这样做时:

  1. 修改 pom.xml 中的打包方式为 <packaging>war</packaging>
  2. 让主应用类继承 SpringBootServletInitializer,这是为了生成符合 Servlet 标准的引导程序。
  3. 打包后的 WAR 文件仍然会包含内嵌的 Tomcat 库,但在部署到外部应用服务器时,这些库会被忽略,应用会使用外部服务器提供的运行时环境。

这就好比你的机甲驾驶员住进了酒店,虽然自带机甲,但酒店规定不准开机,只能使用酒店统一的装备。


结语:理念的进化

WAR 到可执行 JAR,不仅仅是打包方式的改变,更是开发、部署和运维理念的一次重大进化。它标志着Java应用从需要依赖外部环境的"重量级"应用 ,向自我满足、独立统一的"轻量级"应用的转变。

这种转变极大地降低了Spring的开发门槛,简化了部署流程,并为微服务架构的兴起奠定了坚实的技术基础。理解这两种模式背后的哲学,能帮助我们更好地理解Spring Boot的设计之美,并在不同的技术选型中做出最合适的决定。

相关推荐
一枚小小程序员哈41 分钟前
基于Android的车位预售预租APP/基于Android的车位租赁系统APP/基于Android的车位管理系统APP
android·spring boot·后端·struts·spring·java-ee·maven
用户30742971671581 小时前
Spring AI实战:基于ElevenLabs 实现文本转语音的实时音频流
java·spring boot·ai编程
Java水解2 小时前
Spring Boot 事务详解
spring boot·后端
lssjzmn2 小时前
MyBatis Plus 与 MyBatis的PK:Spring Boot 下的详解、选型与实战
spring boot·mybatis
Tom·Ge3 小时前
Spring AI 入门指南:三步将AI集成到Spring Boot应用
人工智能·spring boot·spring
tingting01193 小时前
Spring Boot 外部配置指定不生效的原因与解决
java·spring boot·后端
2501_909686703 小时前
基于SpringBoot的网上点餐系统
java·spring boot·后端
neoooo3 小时前
Spring Boot 3 + Kafka 实战指南
java·spring boot·kafka
设计师小聂!6 小时前
RabbitMQ详解
java·spring boot·分布式·rabbitmq·maven