当前位置: 首页 > ds >正文

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,然后执行一条简单的命令:
    java -jar my-spring-boot-app-0.0.1.jar
    
    这条命令会启动应用内的 main 方法(通常是 SpringApplication.run()),该方法会从内部实例化并启动一个 Tomcat 实例,并将当前应用部署上去。

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

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

2.3 嵌入式容器的魔法

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

<!-- 默认使用 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 JARWAR
启动命令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的设计之美,并在不同的技术选型中做出最合适的决定。

http://www.xdnf.cn/news/18979.html

相关文章:

  • 在 TencentOS 3 上部署 OpenTenBase:从底层原理到生产级实践的深度指南
  • 微服务-24.网关登录校验-实现登录校验
  • 网站开发用什么语言好
  • 数据结构:链式队列尝试;0826
  • 庖丁解牛:深入解析Oracle SQL语言的四大分类——DML、DDL、DCL、TCL
  • Rust 环境搭建与 SeekStorm 项目编译部署(支持中文)
  • Redis相关命令详解及其原理
  • MT** 时间指标全景图:从可靠性到可维护性的度量体系
  • LangGraph-2-Demo
  • CI/CD 全链路实践:从 Git 基础到 Jenkins + GitLab 企业级部署
  • Python 操作 PPT 文件:从新手到高手的实战指南
  • 线性代数中矩阵等价与离散数学中关系的闭包之间的关联
  • VScode,设置自动保存
  • Vue中的props方式
  • 多模态RAG架构:下一代跨模态智能检索系统的设计与实践
  • 视频合成素材视频-多合一功能-青柠剪吧
  • OpenTenBase核心技术解密:突破OLTP与OLAP边界的分布式数据库革新
  • 【PS实战】制作hello标志设计:从选区到色彩填充的完整流程(大学PS作业)
  • 百度IOS客户端岗位--面试真题分析
  • 【Docker基础】Docker-compose进阶配置:资源限制与高可用部署
  • 中国移动云电脑一体机-创维LB2004_瑞芯微RK3566_2G+32G_开ADB安装软件教程
  • Web调用本地程序:Custom URL Scheme
  • Zookeeper(分布式RPC调用和分布式文件储存)
  • 2025年渗透测试面试题总结-35(题目+回答)
  • Radan钣金CAM解决方案在电气柜制造行业的应用案例
  • 物流配送路径规划项目方案
  • 【Doris】服务器配置
  • 深入浅出 ArrayList:从基础用法到底层原理的全面解析(下)
  • IDEA2022开启新版UI
  • 【嵌入式电机控制#进阶4】无感控制(二):观测器导论锁相环(全网最通俗易懂)