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的设计之美,并在不同的技术选型中做出最合适的决定。

相关推荐
后端小张12 小时前
基于飞算AI的图书管理系统设计与实现
spring boot
金銀銅鐵17 小时前
Spring 中的 initializeBean 方法的内部逻辑小总结
spring
考虑考虑1 天前
Jpa使用union all
java·spring boot·后端
阿杆2 天前
同事嫌参数校验太丑,我直接掏出了更优雅的 SpEL Validator
java·spring boot·后端
昵称为空C2 天前
SpringBoot3 http接口调用新方式RestClient + @HttpExchange像使用Feign一样调用
spring boot·后端
麦兜*3 天前
MongoDB Atlas 云数据库实战:从零搭建全球多节点集群
java·数据库·spring boot·mongodb·spring·spring cloud
麦兜*3 天前
MongoDB 在物联网(IoT)中的应用:海量时序数据处理方案
java·数据库·spring boot·物联网·mongodb·spring
汤姆yu3 天前
基于springboot的毕业旅游一站式定制系统
spring boot·后端·旅游
计算机毕业设计木哥3 天前
计算机毕设选题推荐:基于Java+SpringBoot物品租赁管理系统【源码+文档+调试】
java·vue.js·spring boot·mysql·spark·毕业设计·课程设计
青衫客363 天前
Spring异步编程- 浅谈 Reactor 核心操作符
java·spring·响应式编程