重要提示: Spring Boot 默认推荐使用 可执行 JAR (Executable JAR) 包。它内置了 Tomcat/Jetty/Undertow 等 Web 服务器,实现了“应用即服务”的理念,部署极其简单。WAR 包主要用于需要部署到传统外部 Servlet 容器(如 Tomcat, WebLogic)的场景,现在已不常见。


一、核心概念

1. 可执行 JAR (Executable JAR / Fat JAR / Uber JAR)

  • 定义: 一个独立的、包含应用所有代码、依赖库(JARs)、资源文件以及一个嵌入式 Web 服务器(如 Tomcat)的 JAR 文件。
  • 特点:
    • 自包含 (Self-Contained): 所有运行所需的一切都在一个文件里。
    • 内置服务器 (Embedded Server): 不需要外部安装的 Web 服务器。应用自己启动一个 Web 服务器。
    • 易于部署: java -jar your-app.jar 即可启动,非常适合容器化(Docker)和云原生环境。
    • 启动快: 避免了外部容器的启动开销。
    • 版本控制: 应用和其依赖的库版本都打包在一起,避免了“在我机器上能运行”的问题。
  • 文件结构 (使用 jar -tf your-app.jar 查看):
    META-INF/
    META-INF/MANIFEST.MF
    org/
    org/springframework/
    ... (Spring Boot 核心类)
    BOOT-INF/
    BOOT-INF/classes/ (你的应用类和资源)
    BOOT-INF/classes/application.yml
    BOOT-INF/lib/ (所有依赖的 JAR 文件)
    BOOT-INF/lib/spring-boot-starter-web-2.7.14.jar
    BOOT-INF/lib/tomcat-embed-core-9.0.65.jar
    ...
    

2. WAR (Web Application Archive)

  • 定义: 一种传统的 Java Web 应用打包格式,需要部署到外部的 Servlet 容器(如 Apache Tomcat, Jetty, WebLogic, WebSphere)中运行。
  • 特点:
    • 依赖外部容器: 应用本身不包含 Web 服务器,需要外部容器提供。
    • 共享容器资源: 多个 WAR 应用可以部署在同一个容器实例上,共享其资源(内存、线程池等)。
    • 部署复杂: 需要先安装和配置外部容器,然后将 WAR 文件放到容器的 webapps 目录或通过管理界面部署。
    • 启动慢: 需要先启动外部容器,再加载应用。
    • 遗留系统集成: 主要用于需要与旧有系统集成或必须使用特定外部容器的场景。
  • 文件结构 (标准 WAR):
    META-INF/
    META-INF/MANIFEST.MF
    WEB-INF/
    WEB-INF/classes/ (你的应用类和资源)
    WEB-INF/classes/application.yml
    WEB-INF/lib/ (应用的依赖库,但通常排除容器提供的库)
    WEB-INF/lib/your-app-dependency.jar
    WEB-INF/web.xml (可选,Spring Boot 通常不需要)
    index.html (静态资源)
    ...
    

二、操作步骤(非常详细)

步骤 1:创建 Spring Boot 项目

  1. 使用 Spring Initializr:
    • 访问 https://start.spring.io/
    • 选择:
      • Project: Maven ProjectGradle Project
      • Language: Java
      • Spring Boot: 选择一个稳定版本 (如 2.7.14, 3.1.0)
      • Project Metadata:
        • Group: com.example
        • Artifact: demo (这将决定最终 JAR/WAR 的名字)
        • Name: demo
        • Description: Demo project for Spring Boot
        • Package name: com.example.demo
      • Packaging: 默认是 Jar。如果要打 WAR 包,这里可以先选 Jar,后续在 pom.xml 中修改。
      • Java Version: 选择你的 JDK 版本 (如 8, 11, 17)。
    • Dependencies: 添加 Spring Web (用于创建 Web 应用)。
    • 点击 Generate 下载项目压缩包,解压到你的工作目录。

步骤 2:生成可执行 JAR 包 (推荐)

方式 A:使用 Maven

  1. 检查 pom.xml:
    • 确保 <packaging>jar (默认)。
    • 确保有 spring-boot-maven-plugin
      <build>
          <plugins>
              <plugin>
                  <groupId>org.springframework.boot</groupId>
                  <artifactId>spring-boot-maven-plugin</artifactId>
                  <!-- 通常不需要配置,使用默认即可 -->
              </plugin>
          </plugins>
      </build>
      
  2. 构建 JAR:
    • 打开命令行,进入项目根目录 (pom.xml 所在目录)。
    • 执行命令:
      mvn clean package
      # 或
      ./mvnw clean package # (如果使用 mvnw 脚本)
      
  3. 查找 JAR 文件:
    • 构建成功后,会在 target/ 目录下生成 JAR 文件。
    • 文件名通常是 {artifactId}-{version}.jar (如 demo-0.0.1-SNAPSHOT.jar)。
    • 还会生成一个 original-{artifactId}-{version}.jar,这是不包含依赖的原始 JAR。
  4. 运行 JAR:
    • 在命令行执行:
      java -jar target/demo-0.0.1-SNAPSHOT.jar
      
    • 你应该能看到 Spring Boot 启动日志,内嵌的 Tomcat 也启动了。

方式 B:使用 Gradle

  1. 检查 build.gradle:
    • 确保 plugins 块中有 id 'org.springframework.boot' version '...'id 'java'
    • 确保 jar 任务被启用 (通常是默认的)。
    • 确保 bootJar 任务存在 (由 Spring Boot 插件提供):
      plugins {
          id 'org.springframework.boot' version '2.7.14'
          id 'io.spring.dependency-management' version '1.0.15.RELEASE'
          id 'java'
      }
      
      // 确保有这个任务,它会生成可执行 JAR
      tasks.named('bootJar') {
          // 可以在这里配置,但通常不需要
      }
      
  2. 构建 JAR:
    • 打开命令行,进入项目根目录 (build.gradle 所在目录)。
    • 执行命令:
      ./gradlew clean build
      # 或
      gradle clean build
      
    • build 任务会执行 bootJar
  3. 查找 JAR 文件:
    • 构建成功后,会在 build/libs/ 目录下生成 JAR 文件。
    • 文件名通常是 {artifactId}-{version}.jar (如 demo-0.0.1-SNAPSHOT.jar)。
    • 还会生成一个 demo-0.0.1-SNAPSHOT-original.jar,是不包含依赖的原始 JAR。
  4. 运行 JAR:
    • 在命令行执行:
      java -jar build/libs/demo-0.0.1-SNAPSHOT.jar
      

步骤 3:生成 WAR 包 (用于外部容器)

注意: 除非有特殊要求,否则不推荐。JAR 包是首选。

  1. 修改打包方式 (pom.xmlbuild.gradle):

    • Maven (pom.xml):
      • <packaging>jar</packaging> 修改为 <packaging>war</packaging>
      • 排除嵌入式 Tomcat: 因为外部容器会提供 Servlet API,需要将 spring-boot-starter-tomcat 的范围设置为 provided,避免冲突。
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
                <!-- 排除嵌入式 Tomcat -->
                <exclusions>
                    <exclusion>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-starter-tomcat</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
            <!-- 添加 Servlet API 依赖,范围为 provided -->
            <dependency>
                <groupId>javax.servlet</groupId>
                <artifactId>javax.servlet-api</artifactId>
                <scope>provided</scope>
            </dependency>
            <!-- 如果是 Spring Boot 3.x, 使用 jakarta.servlet -->
            <!--
            <dependency>
                <groupId>jakarta.servlet</groupId>
                <artifactId>jakarta.servlet-api</artifactId>
                <scope>provided</scope>
            </dependency>
            -->
        </dependencies>
        
    • Gradle (build.gradle):
      • 应用 war 插件:
        plugins {
            id 'org.springframework.boot' version '2.7.14'
            id 'io.spring.dependency-management' version '1.0.15.RELEASE'
            id 'java'
            id 'war' // 添加 war 插件
        }
        
      • 排除嵌入式 Tomcat:
        dependencies {
            implementation('org.springframework.boot:spring-boot-starter-web') {
                exclude group: 'org.springframework.boot', module: 'spring-boot-starter-tomcat'
            }
            providedRuntime 'javax.servlet:javax.servlet-api:4.0.1' // 对于 Boot 3.x, 使用 jakarta.servlet-api
        }
        
  2. 创建 WAR 部署入口类 (仅 Maven 需要,Gradle 通常不需要):

    • src/main/java/com/example/demo/ 目录下创建一个类,继承 SpringBootServletInitializer 并重写 configure 方法。
    • ServletInitializer.java:
      package com.example.demo;
      
      import org.springframework.boot.builder.SpringApplicationBuilder;
      import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
      
      public class ServletInitializer extends SpringBootServletInitializer {
      
          @Override
          protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
              return application.sources(DemoApplication.class); // 指向你的主应用类
          }
      
      }
      
    • 说明: 这个类告诉外部 Servlet 容器如何启动 Spring Boot 应用。Gradle 的 war 插件通常会自动处理。
  3. 构建 WAR:

    • Maven:
      mvn clean package
      # 在 target/ 目录下生成 demo-0.0.1-SNAPSHOT.war
      
    • Gradle:
      ./gradlew clean build
      # 在 build/libs/ 目录下生成 demo-0.0.1-SNAPSHOT.war
      
  4. 部署到外部 Tomcat:

    • 下载并安装 Apache Tomcat (如 9.x)。
    • 启动 Tomcat (bin/startup.shbin/startup.bat)。
    • 将生成的 demo-0.0.1-SNAPSHOT.war 文件复制到 Tomcat 的 webapps/ 目录下。
    • Tomcat 会自动解压 WAR 文件并部署应用(或通过 Tomcat Manager Web 界面部署)。
    • 访问 http://localhost:8080/demo-0.0.1-SNAPSHOT/ (URL 路径通常与 WAR 文件名一致,除非在 application.yml 中配置了 server.servlet.context-path)。

三、常见错误

  1. java.lang.NoClassDefFoundError: org/springframework/boot/SpringApplication:

    • 原因: 构建的 JAR 是普通的 JAR,不是可执行 JAR。MANIFEST.MF 文件中缺少 Main-ClassStart-Class
    • 解决: 确保使用了 spring-boot-maven-plugin (Maven) 或 bootJar 任务 (Gradle),并且插件配置正确。
  2. Port already in use: 8080:

    • 原因: 端口被占用。
    • 解决: 修改 application.yml 中的 server.port,或杀死占用端口的进程。
  3. Error: Could not find or load main class com.example.demo.DemoApplication:

    • 原因: JAR 文件损坏,或 MANIFEST.MF 中的 Main-Class 指向了错误的类。
    • 解决: 重新构建 JAR,检查 MANIFEST.MF 内容。
  4. WAR 包部署后 404:

    • 原因 1: ServletInitializer 类缺失或配置错误 (Maven 项目)。
    • 解决: 检查 ServletInitializer.java 是否存在且正确继承和重写。
    • 原因 2: spring-boot-starter-tomcat 未正确排除,导致与外部容器的 Servlet API 冲突。
    • 解决: 检查 pom.xmlbuild.gradle 的依赖排除。
    • 原因 3: WAR 文件未正确复制到 webapps 目录,或 Tomcat 未自动部署。
    • 解决: 检查文件路径,查看 Tomcat 日志。
  5. java.lang.ClassNotFoundException: javax.servlet.ServletContextListener:

    • 原因: 缺少 javax.servlet-api 依赖,且范围不是 provided
    • 解决: 确保在 pom.xmlbuild.gradle 中添加了 javax.servlet:javax.servlet-api 依赖,并且 scopeprovided (Maven) 或 providedRuntime (Gradle)。
  6. Failed to load ApplicationContext:

    • 原因: 配置文件 (application.yml) 错误,或 Bean 注入失败。
    • 解决: 检查配置文件语法,检查 @ComponentScan 路径,检查依赖注入的 Bean 是否存在。

四、注意事项

  1. JAR 是首选: 除非有明确要求必须使用外部容器,否则始终选择可执行 JAR
  2. MANIFEST.MF: 可执行 JAR 的 META-INF/MANIFEST.MF 文件至关重要,它包含 Main-Class: org.springframework.boot.loader.JarLauncherStart-Class: com.yourpackage.YourApplication。Spring Boot 插件会自动生成。
  3. 依赖范围 (scope): 打 WAR 包时,spring-boot-starter-tomcat 必须被排除,javax.servlet-api (或 jakarta.servlet-api) 的范围必须是 provided
  4. ServletInitializer: Maven 项目打 WAR 包时通常需要这个类。Gradle 项目通常不需要。
  5. 上下文路径 (context-path):application.yml 中可以通过 server.servlet.context-path=/myapp 设置应用的访问路径。部署到外部容器时,WAR 文件名也会影响路径。
  6. 外部配置: 可以通过 --spring.config.location=...SPRING_CONFIG_LOCATION 环境变量在运行时覆盖 JAR 内的配置。
  7. JDK 版本: 确保运行 JAR/WAR 的 JDK 版本与编译时的版本兼容。

五、使用技巧

  1. 查看 JAR 内容:
    • jar -tf your-app.jar (列出所有文件)
    • jar -xf your-app.jar META-INF/MANIFEST.MF (解压特定文件)
  2. 传递 JVM 参数:
    • java -Xms512m -Xmx1024m -jar your-app.jar --server.port=9090
  3. 传递 Spring Boot 参数:
    • java -jar your-app.jar --spring.profiles.active=prod --logging.level.root=DEBUG
  4. 后台运行:
    • Linux: nohup java -jar your-app.jar > app.log 2>&1 &
    • 使用 screentmux
  5. 使用启动脚本: 编写 shell 脚本 (start.sh, stop.sh) 来管理应用的启动、停止和状态检查。
  6. Docker 化: 将 JAR 包放入 Docker 镜像是最常见的部署方式。
    FROM openjdk:11-jre-slim
    COPY target/demo-0.0.1-SNAPSHOT.jar app.jar
    EXPOSE 8080
    ENTRYPOINT ["java", "-jar", "/app.jar"]
    
  7. 分离依赖库 (可选): 对于超大应用,可以配置 spring-boot-maven-plugin 将依赖库复制到 lib/ 目录,减少主 JAR 大小,但会增加部署复杂性。
    <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
        <configuration>
            <layout>ZIP</layout> <!-- 或 MODULES -->
            <addStartScripts>false</addStartScripts>
        </configuration>
    </plugin>
    

六、最佳实践

  1. 默认使用可执行 JAR: 这是 Spring Boot 的设计哲学和最佳实践。
  2. 版本化构建:pom.xml / build.gradle 中使用明确的版本号,便于追踪和回滚。
  3. 自动化构建: 使用 CI/CD 工具 (如 Jenkins, GitLab CI, GitHub Actions) 自动执行 mvn clean package./gradlew build
  4. Docker 优先: 将构建好的 JAR 包制作成 Docker 镜像,实现环境一致性。
  5. 健康检查: 确保应用暴露 /actuator/health 端点,便于监控。
  6. 日志配置:application.yml 中配置日志级别和输出位置 (logging.file.name=app.log)。
  7. 配置外部化: 将敏感配置(密码、密钥)通过环境变量或配置中心管理,不要硬编码在 application.yml 中。
  8. 资源优化: 对于 WAR 包,确保 provided 依赖正确,避免打包不必要的库。

七、性能优化

  1. JAR 包大小优化:
    • 分析依赖: 使用 mvn dependency:tree./gradlew dependencies 查看依赖树,移除未使用的依赖。
    • 使用精简依赖: 选择功能更专注的 Starter,避免引入庞大的依赖。
    • 排除传递依赖: 如果某个依赖的传递依赖你不需要,可以排除它。
    • 使用 spring-boot-thin-layout: 一个实验性插件,可以生成更小的 JAR,依赖在运行时下载。
  2. 启动速度优化:
    • 减少自动配置: 使用 @EnableAutoConfiguration(exclude = {...})spring.autoconfigure.exclude 排除不需要的自动配置类。
    • 延迟初始化 (Lazy Initialization):application.yml 中设置 spring.main.lazy-initialization=true,让 Bean 在首次使用时才创建。
    • 使用 GraalVM Native Image: 将 Spring Boot 应用编译成本地可执行文件,启动速度极快(毫秒级),内存占用小。但构建复杂,兼容性需验证。
  3. 运行时性能:
    • JVM 调优: 根据应用负载合理设置 -Xms, -Xmx, 选择合适的 GC 算法 (-XX:+UseG1GC)。
    • 监控: 使用 spring-boot-actuator + Micrometer 监控应用性能指标(CPU, 内存, HTTP 延迟, 数据库连接等)。
  4. 部署优化:
    • 蓝绿部署/金丝雀发布: 使用 JAR 包 + Docker + Kubernetes 可以轻松实现滚动更新,减少停机时间。
    • 缓存: 利用 Spring Cache 抽象缓存频繁访问的数据。

总结

  • 核心概念: Spring Boot 可执行 JAR 是自包含、内置服务器的独立应用包;WAR 是依赖外部 Servlet 容器的传统 Web 包。
  • 操作步骤: JAR 包只需 mvn clean package./gradlew build,然后 java -jarWAR 包需修改 pom.xml/build.gradle (改 packagingwar,排除 tomcat starter,添加 servlet-api provided),Maven 项目通常需 ServletInitializer,然后构建并部署到外部容器。
  • 常见错误: JAR 找不到主类、端口占用、WAR 部署 404(缺少 ServletInitializer 或依赖冲突)。
  • 注意事项: JAR 是绝对首选;注意 MANIFEST.MF、依赖范围 (provided)、ServletInitializer
  • 使用技巧: jar -tf 查看内容,传递 JVM/Spring 参数,后台运行,Docker 化。
  • 最佳实践: 默认用 JAR,CI/CD 自动化,Docker 优先,外部化配置。
  • 性能优化: 优化 JAR 大小(分析依赖),加快启动(延迟初始化、Native Image),JVM 调优,监控。