15.2 打包部署应用

Spring Boot 项目可以如传统的web应用一样打包成war包,部署到不同的应用服务器(如tomcat,jetty,WebSphere Application Server,WebLogic等)中,也可以使用内嵌服务器(tomcat,jetty,Undertow 等开源容器),以直接可运行的可执行jar包(Spring Boot 官方称之为fat jar)方式部署。

由于我们在绝大多数情况下使用 Spring Boot 开发前后端分离的企业级应用,所以这里介绍的 Spring Boot 项目特指 Spring Boot Web项目,即使用 Spring MVC 通过REST方式在HTTP上提供服务的后端项目。

本小节对这两种打包部署方式进行介绍。

官方文档介绍,其支持 Maven 和 Gradle 两种构建系统,本小节只介绍我们在项目中经常使用的 Maven 构建系统。

在Maven构建系统中,Spring Boot 是通过 Maven 插件的方式提供打包支持的。

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

15.2.1 Fat JAR

为了分析 Spring Boot Fat JAR 的启动机制,添加“spring-boot-loader”依赖,以便打开 Spring Boot 对应的源码。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-loader</artifactId>
    <scope>provided</scope>
</dependency>

在 Spring Boot 项目的pom.xml文件中指定项目打包类型为 jar(<packaging>jar</packaging>,不提供打包类型,默认就是打包为jar),然后添加spring-boot-maven-plugin插件,在项目上运行 Maven Build...

image-20200131144147470

在 Goals 输入package 构建命令(可选跳过测试),然后运行。Maven就开始编译打包。

在STS中新建 Spring Starter 项目,默认就添加了spring-boot-maven-plugin插件。

image-20200131142025288

Maven 打包完成后,在项目的 target 目录中就生成了两个jar包:

  • config-0.0.1-SNAPSHOT.jar.original:原始的jar包,包含项目代码编译后的内容;
  • config-0.0.1-SNAPSHOT.jar:spring-boot-maven-plugin 插件添加依赖的其他jar包(如内嵌tomcat服务器,Spring MVC,MySQL驱动等)的“Fat Jar”文件。

image-20200131142129791

使用 WinRAR 压缩工具打开这个 Fat Jar 文件,可以看到在BOOT-INF下的lib子目录下包含了tomcat内嵌服务器及其他依赖jar包。在 BOOT-INF\classes 目录下就是我们在项目中配置文件、Java 类编译后的 class 字节码文件。

image-20200131144913756

├── BOOT-INF # 存放项目中开发的业务相关类和配置文件,和依赖的jar
│   ├── classes # 项目业务代码及配置文件
│   └── lib # 存放项目的依赖库,如Spring MVC,MySQL JDBC驱动,Tomcat内嵌服务器
├── META-INF # 包括 MANIFEST.MF 描述文件和 maven 的构建信息
│   ├── MANIFEST.MF
│   └── maven
└── org # Spring Boot 相关的类
    └── springframework

根据 Java jar 的运行规范,查看/META-INF/MANIFEST.MF,找到的Main-Class是org.springframework.boot.loader.JarLauncher,这是 Spring Boot 提供的一个启动类。

Manifest-Version: 1.0
Implementation-Title: spring-boot-config
Implementation-Version: 0.0.1-SNAPSHOT
Start-Class: com.example.config.SpringBootConfigApplication
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Build-Jdk-Spec: 1.8
Spring-Boot-Version: 2.2.4.RELEASE
Created-By: Maven Archiver 3.4.0
Main-Class: org.springframework.boot.loader.JarLauncher

image-20200131150539889

打开 JarLauncher 源码,可以看到其继承自 ExecutableArchiveLauncher 类,并提供了 "BOOT-INF/classes/" 和 "BOOT-INF/lib/" 常量。

public class JarLauncher extends ExecutableArchiveLauncher {

    static final String BOOT_INF_CLASSES = "BOOT-INF/classes/";

    static final String BOOT_INF_LIB = "BOOT-INF/lib/";

    public JarLauncher() {
    }

    protected JarLauncher(Archive archive) {
        super(archive);
    }

    @Override
    protected boolean isNestedArchive(Archive.Entry entry) {
        if (entry.isDirectory()) {
            return entry.getName().equals(BOOT_INF_CLASSES);
        }
        return entry.getName().startsWith(BOOT_INF_LIB);
    }

    public static void main(String[] args) throws Exception {
        new JarLauncher().launch(args);
    }

}

查看 ExecutableArchiveLauncher 类,可以看到 getMainClass 方法获取 Start-Class(/META-INF/MANIFEST.MF文件中提供的:Start-Class: com.example.config.SpringBootConfigApplication),这个就是我们在项目中用 @SpringBootApplication 注解标注的 SpringBootConfigApplication 启动类。

    @Override
    protected String getMainClass() throws Exception {
        Manifest manifest = this.archive.getManifest();
        String mainClass = null;
        if (manifest != null) {
            mainClass = manifest.getMainAttributes().getValue("Start-Class");
        }
        if (mainClass == null) {
            throw new IllegalStateException("No 'Start-Class' manifest entry specified in " + this);
        }
        return mainClass;
    }

简单的说,jar 包中的 Main-Class JarLauncher 找到 Start-Class(就是@SpringBootApplication 注解标注的的启动类),使用 Fat JAR 文件中 BOOT-INF/lib/ 路径下的依赖jar包,运行 Spring Boot 应用程序。

一旦打包好了 Fat JAR,就可以 ssh 到工作机/堡垒机然后通过 scp 将这个Fat JAR 拷贝到部署服务器上,通过nohup java -jar xxx.jar & 即可启动 Spring Boot 应用。以上几个命令,都为 Linux 下的命令。

本小节示例项目代码:

https://github.com/gyzhang/SpringBootCourseCode/tree/master/spring-boot-config

15.2.2 WAR

在较少遇到的一些特殊情况下,我们需要将 Spring Boot 后端项目打包部署到客户指定的服务器上,比如WebSphere Application Server、WebLogic上,这种传统部署方式就需要传统的war包。

Spring Starter 新建项目向导提供了完善的支持,可以快速的开发测试项目,并将项目打包为传统war包。

首先创建一个 Spring Boot 项目,打包类型选择“War”。

image-20200131170504335

选择 Spring Web 启动器依赖。

image-20200131170524454

Spring Starter新建项目向导为项目pom文件中添加打包类型<packaging>war</packaging>spring-boot-starter-tomcat依赖。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.4.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>war</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>war</packaging>
    <name>spring-boot-war</name>
    <description>Spring Boot War Example.</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

Spring 为我们生成了一个ServletInitializer类,在 configure 方法中完成初始化操作。

package com.example.war;

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(SpringBootWarApplication.class);
    }

}

在开发环境下可按照“Spring Boot App”方式正常运行,和传统内嵌tomcat容器运行方式一样。

image-20200131172725431

服务器启动后,访问http://localhost:8080/hello/greeting?name=Kevin可按照预期返回结果。

image-20200131172106076

也可以在 STS 中添加 tomcat 9,然后选择“Run on Server”按照传统Web应用开发方式运行。

在 STS 内打开的浏览器地址栏中输入http://localhost:8080/war/hello/greeting?name=Kevin可正确的返回期望的结果。

image-20200131175814160

执行 maven package 打包操作后,在项目 target 目录下生成两个 war 包文件,和传统内嵌web容器的 jar 包类似,一个为原始封包,一个为可运行 war 包。

image-20200131174041012

通过 WinRAR 打开 war-0.0.1-SNAPSHOT.war 文件,可以看到熟悉的 war 包结构和 Spring Boot 的 Fat JAR 的 org.springframework.boot.loader.JarLauncher 类等类似结构。

image-20200131174404243

查看“/META-INF/MANIFEST.MF”文件,可以看到 Main-Class 变更为 Main-Class: org.springframework.boot.loader.WarLauncher ,通过 java -jar war-0.0.1-SNAPSHOT.war 可运行内嵌服务器版的 Spring Boot war 应用。

Manifest-Version: 1.0
Implementation-Title: spring-boot-war
Implementation-Version: 0.0.1-SNAPSHOT
Start-Class: com.example.war.SpringBootWarApplication
Spring-Boot-Classes: WEB-INF/classes/
Spring-Boot-Lib: WEB-INF/lib/
Build-Jdk-Spec: 1.8
Spring-Boot-Version: 2.2.4.RELEASE
Created-By: Maven Archiver 3.4.0
Main-Class: org.springframework.boot.loader.WarLauncher

将打包后的 war-0.0.1-SNAPSHOT.war 文件拷贝到“apache-tomcat-9.0.30\webapps”目录下,启动 tomcat,打开浏览器访问http://localhost:8080/war-0.0.1-SNAPSHOT/hello/greeting?name=Kevin可正确的返回期望的结果。

image-20200131180159588

Spring Boot 为我们打包的war文件,即可单独运行(使用内嵌web容器),也可以部署到web服务器(如tomcat 9)中运行。

所以,我们可以将 Spring Boot 打包出来的 war 包称之为 Fat WAR

本小节示例项目代码:

https://github.com/gyzhang/SpringBootCourseCode/tree/master/spring-boot-war

results matching ""

    No results matching ""