文章目录

  • 一、场景描述
  • 二、打包方式
  • 场景方式一:常规 MAVEN 零散项目打包部署方式
  • 场景方式二:常规 MAVEN 集中项目打包部署方式
  • 场景方式三:借助 spring-boot 插件进行打包部署


一、场景描述
  • 采用父子工程的模块化管理,可以解决模块版本和模块间依赖的大多数场景和问题
  • 采用 spring-boot 插件进行项目的打包和部署,可以解决九成的项目持续集成和部署问题
  • 在项目开发过程中,随着业务的不断复杂和模块的不断增加,一种打包和部署方式可能难以满足,所以我们需要掌握不同项目的打包和部署方式,来应对变化多端的场景和需求
二、打包方式
场景方式一:常规 MAVEN 零散项目打包部署方式
  1. 新建 Maven 父子工程(此模式一般都是一个一个单独分开、零散的模块的场景,这里为了演示方便,创建来父子工程
example
    - example-common
        - CommonUtil.java
        - pom.xml
    - example-service
        - ServiceApplication.java
        - pom.xml
- pom.xml

P.S

  • exampleexample-commonexample-service 的父工程
  • example-service 依赖 example-common 模块
  • 所有模块的 pom 文件中不加入任何的 Maven 打包插件
  • 父工程 pom
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <!-- 模块坐标信息 GAV -->
    <groupId>com.rambo</groupId>
    <artifactId>example</artifactId>
    <packaging>pom</packaging>
    <version>V1.0.0.1</version>

    <name>${project.artifactId}</name>
    <description>父子模块示例工程 —— 基础父工程</description>

    <!-- 子模块列表 -->
    <modules>
        <module>example-common</module>
        <module>example-service</module>
    </modules>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>

    <build>
        <finalName>${project.artifactId}</finalName>
    </build>
</project>
  • example-common 模块 pom
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <!-- 父工程 GAV -->
    <parent>
        <artifactId>example</artifactId>
        <groupId>com.rambo</groupId>
        <version>V1.0.0.1</version>
    </parent>

    <!-- 本工程模块 AV -->
    <artifactId>example-common</artifactId>
    <version>V1.0.0.1</version>
    <packaging>jar</packaging>

    <name>${project.artifactId}</name>
    <description>无启动类的示例通用模块 —— 通用工具</description>
</project>
  • example-service 模块 pom
<?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>

    <!-- 父工程 GAV -->
    <parent>
        <artifactId>example</artifactId>
        <groupId>com.rambo</groupId>
       <version>V1.0.0.1</version>
    </parent>

    <!-- 本工程模块 AV -->
    <artifactId>example-service</artifactId>
    <version>V1.0.0.1</version>
    <packaging>jar</packaging>

    <name>example-service</name>
    <description>有启动类的示例服务模块 —— 示例服务</description>

    <dependencies>
        <!-- 示例通用工具模块 -->
        <dependency>
            <groupId>com.rambo</groupId>
            <artifactId>example-common</artifactId>
            <version>V1.0.0.1</version>
        </dependency>
    </dependencies>
</project>
  1. example-commonexample-service 的示例代码如下
  • example-common 模块
public class CommonUtil {
    public static void info() {
        System.out.println("This is info from CommonUtil.class");
    }
}
  • example-service 模块
public class ServiceApplication {
    public static void main(String[] args) {
        CommonUtil.info();
    }
}
  1. 在父工程的 pom 文件同级执行 Maven 打包命令
  • 打包命令
mvn package
  • 两个模块的 MANIFEST.MF 文件内容如下
Manifest-Version: 1.0
Archiver-Version: Plexus Archiver
Built-By: rambo
Created-By: Apache Maven 3.6.3
Build-Jdk: 1.8.0_271
  1. 将上一步打出来的 jar 包放在同一个目录中,执行以下命令启动程序
  • 执行命令
java -classpath example-common.jar:example-service.jar com.rambo.service.ServiceApplication
  • 运行结果
 ~/WorkSpace/example/example-service/target/ java -classpath example-common.jar:example-service.jar com.rambo.service.ServiceApplication 
This is info from CommonUtil.class
 ~/WorkSpace/example/example-service/target/
  • 命令解释
  • 语法:java -classpath xxx.jar:yyy.jar:zzz.jar mainClassName
  • java -classpath :为固定部分
  • xxx.jar:yyy.jar:zzz.jar : 多个依赖 jar: 隔开
  • mainClassName : 为 main 方法所在类的类名
场景方式二:常规 MAVEN 集中项目打包部署方式
  1. 示例工程还是采用第一步创建的父子工程
  2. 之所以说是集中项目打包的使用场景:项目创建好以后,不会随意的新增或者删减模块(言外之意就是,AB 模块的创建和依赖关系固定)
  3. example-service 模块 pom 文件中添加如下插件
<build>
    <plugins>
        <!-- 采用 maven-jar-plugin 插件来自定义 MANIFEST.MF 文件 -->
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-jar-plugin</artifactId>
            <configuration>
                <finalName>${project.artifactId}</finalName>
                <archive>
                    <manifest>
                        <!-- 设置 MANIFEST.MF 文件中的主启动类 -->
                        <mainClass>com.rambo.service.ServiceApplication</mainClass>
                        <!-- 将需要依赖的 JAR 包,添加到 MANIFEST.MF 文件中 -->
                        <addClasspath>true</addClasspath>
                        <!-- 依赖 JAR 包读取的文件夹前缀 -->
                        <classpathPrefix>lib</classpathPrefix>
                    </manifest>
                </archive>
            </configuration>
        </plugin>
        <!-- 拷贝依赖插件,也可以通过 mvn dependency:copy-dependencies -DoutputDirectory=/path/to/lib -DincludeScope=runtime 命令来收集依赖包 -->
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-dependency-plugin</artifactId>
            <executions>
                <execution>
                    <id>copy-dependencies</id>
                    <phase>package</phase>
                    <goals>
                        <goal>copy-dependencies</goal>
                    </goals>
                    <configuration>
                        <outputDirectory>${project.build.directory}/lib/</outputDirectory>
                    </configuration>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>
  1. 在父工程的 pom 文件同级执行 Maven 打包命令
  • 打包命令
mvn package
  • example-common 模块的 MANIFEST.MF 文件内容如下
Manifest-Version: 1.0
Archiver-Version: Plexus Archiver
Built-By: rambo
Created-By: Apache Maven 3.6.3
Build-Jdk: 1.8.0_271
  • example-service 模块的 MANIFEST.MF 文件内容如下
Manifest-Version: 1.0
Archiver-Version: Plexus Archiver
Built-By: rambo
Class-Path: lib/example-common-V1.0.0.1.jar
Created-By: Apache Maven 3.6.3
Build-Jdk: 1.8.0_271
Main-Class: com.rambo.service.ServiceApplication
  • Class-Path: lib/example-common-V1.0.0.1.jar 应用程序所需依赖所在位置
  • Main-Class: com.rambo.service.ServiceApplication 应用程序主启动类所在位置
  1. maven-dependency-plugin 插件将所有依赖包都已经自动归档在 example-service 同一个目录的 lib 文件夹中,执行以下命令启动程序
 ~/WorkSpace/example/example-service/target/ java -jar example-service.jar
This is info from CommonUtil.class
 ~/WorkSpace/example/example-service/target/
场景方式三:借助 spring-boot 插件进行打包部署
  1. 前面两种打包部署的方式中,主程序和依赖的 JAR 一般都是零散、分离的,这在云环境部署会比较麻烦,需要上传许多的依赖包(或者统一压缩并在云服务器解压),单机部署还好,如果分布式部署就会很累
  2. 为解决上述问题,我们可以打 fat jar 来解决
  3. 在有启动类的 example-service 模块将 maven-jar-plugin 插件替换为 spring-boot-maven 打包插件
<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <version>${spring-boot.version}</version>
            <executions>
                <execution>
                    <id>repackage</id>
                    <goals>
                        <goal>repackage</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>
  1. 在父工程的 pom 文件同级执行 Maven 打包命令
  • 打包命令
mvn package
  • example-common 模块的 MANIFEST.MF 文件内容如下
Manifest-Version: 1.0
Archiver-Version: Plexus Archiver
Built-By: rambo
Created-By: Apache Maven 3.6.3
Build-Jdk: 1.8.0_271
  • example-service 模块的 MANIFEST.MF 文件内容如下
Manifest-Version: 1.0
Spring-Boot-Classpath-Index: BOOT-INF/classpath.idx
Archiver-Version: Plexus Archiver
Built-By: rambo
Spring-Boot-Layers-Index: BOOT-INF/layers.idx
Start-Class: com.rambo.service.ServiceApplication
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Spring-Boot-Version: 2.5.2
Created-By: Apache Maven 3.6.3
Build-Jdk: 1.8.0_271
Main-Class: org.springframework.boot.loader.JarLauncher
  1. 通过此打包方式,example-serviceJAR 可以直接运行,在其同级目录下可以看到一个 example-service.jar.original 文件
 ~/WorkSpace/example/example-service/target/ pwd                                
/Users/rambo/WorkSpace/example/example-service/target
 ~/WorkSpace/example/example-service/target/ java -jar example-service.jar      
This is info from CommonUtil.class
 ~/WorkSpace/example/example-service/target/

P.S

为什么可以直接运行?因为采用 spring-boot-maven 插件打出来的 JAR 包是一个 fat jar ,里面包含了被依赖的所有的 JAR 包程序,再通过 MANIFEST.MFJarLauncher 即可启动应用程序

  1. 关于 example-service.jar.originalexample-service.jar 的理解
  • 查看 .original 文件的依赖
 ~/WorkSpace/example/example-service/target/ jar tf example-service.jar.original
META-INF/
META-INF/MANIFEST.MF
com/
com/rambo/
com/rambo/service/
application.yml
com/rambo/service/ServiceApplication.class
META-INF/maven/
META-INF/maven/com.rambo/
META-INF/maven/com.rambo/example-service/
META-INF/maven/com.rambo/example-service/pom.xml
META-INF/maven/com.rambo/example-service/pom.properties
 ~/WorkSpace/example/example-service/target/
  • 查看 jar 的依赖
 ~/WorkSpace/example/example-service/target/ jar tf example-service.jar         
META-INF/
META-INF/MANIFEST.MF
org/
org/springframework/
org/springframework/boot/
org/springframework/boot/loader/
org/springframework/boot/loader/ClassPathIndexFile.class
org/springframework/boot/loader/ExecutableArchiveLauncher.class
org/springframework/boot/loader/JarLauncher.class
org/springframework/boot/loader/LaunchedURLClassLoader$DefinePackageCallType.class
org/springframework/boot/loader/LaunchedURLClassLoader$UseFastConnectionExceptionsEnumeration.class
org/springframework/boot/loader/LaunchedURLClassLoader.class
org/springframework/boot/loader/Launcher.class
org/springframework/boot/loader/MainMethodRunner.class
org/springframework/boot/loader/PropertiesLauncher$1.class
org/springframework/boot/loader/PropertiesLauncher$ArchiveEntryFilter.class
org/springframework/boot/loader/PropertiesLauncher$ClassPathArchives.class
org/springframework/boot/loader/PropertiesLauncher$PrefixMatchingArchiveFilter.class
org/springframework/boot/loader/PropertiesLauncher.class
org/springframework/boot/loader/WarLauncher.class
org/springframework/boot/loader/archive/
org/springframework/boot/loader/archive/Archive$Entry.class
org/springframework/boot/loader/archive/Archive$EntryFilter.class
org/springframework/boot/loader/archive/Archive.class
org/springframework/boot/loader/archive/ExplodedArchive$AbstractIterator.class
org/springframework/boot/loader/archive/ExplodedArchive$ArchiveIterator.class
org/springframework/boot/loader/archive/ExplodedArchive$EntryIterator.class
org/springframework/boot/loader/archive/ExplodedArchive$FileEntry.class
org/springframework/boot/loader/archive/ExplodedArchive$SimpleJarFileArchive.class
org/springframework/boot/loader/archive/ExplodedArchive.class
org/springframework/boot/loader/archive/JarFileArchive$AbstractIterator.class
org/springframework/boot/loader/archive/JarFileArchive$EntryIterator.class
org/springframework/boot/loader/archive/JarFileArchive$JarFileEntry.class
org/springframework/boot/loader/archive/JarFileArchive$NestedArchiveIterator.class
org/springframework/boot/loader/archive/JarFileArchive.class
org/springframework/boot/loader/data/
org/springframework/boot/loader/data/RandomAccessData.class
org/springframework/boot/loader/data/RandomAccessDataFile$1.class
org/springframework/boot/loader/data/RandomAccessDataFile$DataInputStream.class
org/springframework/boot/loader/data/RandomAccessDataFile$FileAccess.class
org/springframework/boot/loader/data/RandomAccessDataFile.class
org/springframework/boot/loader/jar/
org/springframework/boot/loader/jar/AbstractJarFile$JarFileType.class
org/springframework/boot/loader/jar/AbstractJarFile.class
org/springframework/boot/loader/jar/AsciiBytes.class
org/springframework/boot/loader/jar/Bytes.class
org/springframework/boot/loader/jar/CentralDirectoryEndRecord$1.class
org/springframework/boot/loader/jar/CentralDirectoryEndRecord$Zip64End.class
org/springframework/boot/loader/jar/CentralDirectoryEndRecord$Zip64Locator.class
org/springframework/boot/loader/jar/CentralDirectoryEndRecord.class
org/springframework/boot/loader/jar/CentralDirectoryFileHeader.class
org/springframework/boot/loader/jar/CentralDirectoryParser.class
org/springframework/boot/loader/jar/CentralDirectoryVisitor.class
org/springframework/boot/loader/jar/FileHeader.class
org/springframework/boot/loader/jar/Handler.class
org/springframework/boot/loader/jar/JarEntry.class
org/springframework/boot/loader/jar/JarEntryCertification.class
org/springframework/boot/loader/jar/JarEntryFilter.class
org/springframework/boot/loader/jar/JarFile$1.class
org/springframework/boot/loader/jar/JarFile$JarEntryEnumeration.class
org/springframework/boot/loader/jar/JarFile.class
org/springframework/boot/loader/jar/JarFileEntries$1.class
org/springframework/boot/loader/jar/JarFileEntries$EntryIterator.class
org/springframework/boot/loader/jar/JarFileEntries.class
org/springframework/boot/loader/jar/JarFileWrapper.class
org/springframework/boot/loader/jar/JarURLConnection$1.class
org/springframework/boot/loader/jar/JarURLConnection$JarEntryName.class
org/springframework/boot/loader/jar/JarURLConnection.class
org/springframework/boot/loader/jar/StringSequence.class
org/springframework/boot/loader/jar/ZipInflaterInputStream.class
org/springframework/boot/loader/jarmode/
org/springframework/boot/loader/jarmode/JarMode.class
org/springframework/boot/loader/jarmode/JarModeLauncher.class
org/springframework/boot/loader/jarmode/TestJarMode.class
org/springframework/boot/loader/util/
org/springframework/boot/loader/util/SystemPropertyUtils.class
BOOT-INF/
BOOT-INF/classes/
BOOT-INF/classes/com/
BOOT-INF/classes/com/rambo/
BOOT-INF/classes/com/rambo/service/
BOOT-INF/classes/application.yml
BOOT-INF/classes/com/rambo/service/ServiceApplication.class
META-INF/maven/
META-INF/maven/com.rambo/
META-INF/maven/com.rambo/example-service/
META-INF/maven/com.rambo/example-service/pom.xml
META-INF/maven/com.rambo/example-service/pom.properties
BOOT-INF/lib/
BOOT-INF/lib/example-common-V1.0.0.1.jar
BOOT-INF/lib/lombok-1.18.20.jar
BOOT-INF/lib/spring-boot-jarmode-layertools-2.5.2.jar
BOOT-INF/classpath.idx
BOOT-INF/layers.idx
 ~/WorkSpace/example/example-service/target/
  1. 从上述结果可以看出,在 example-service 打包出来的依赖来看, BOOT-INF/lib/example-common-V1.0.0.1.jar 已被包含在 JAR 包中了,所以直接通过 java -jar example-service.jar 是可以直接运行的
  2. 关于 fat jar 的结构和运行方式,请期待下一篇文章《Spring Boot 代理启动 —— JarLauncher & WarLauncher》的讲述。