JAVA 项目打包和部署一站式解决方案

一、场景描述
  • 采用父子工程的模块化管理,可以解决模块版本和模块间依赖的大多数场景和问题

  • 采用 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>
      
  2. 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();
          }
      }
      
  3. 在父工程的 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
      
  4. 将上一步打出来的 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>
    
  4. 在父工程的 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 应用程序主启动类所在位置

  5. 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>
    
  4. 在父工程的 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
      
  5. 通过此打包方式,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 即可启动应用程序

  6. 关于 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/ 
      
  7. 从上述结果可以看出,在 example-service 打包出来的依赖来看, BOOT-INF/lib/example-common-V1.0.0.1.jar 已被包含在 JAR 包中了,所以直接通过 java -jar example-service.jar 是可以直接运行的

  8. 关于 fat jar 的结构和运行方式,请期待下一篇文章《Spring Boot 代理启动 —— JarLauncher & WarLauncher》的讲述。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值