使得项目易于构建部署,适应不同场景下的复杂工程的组织、发行、交付、落地中的问题。多场景部署,优化部署流程,减少不必要的构建和文件复制传输。

背景

一个多模块项目,一级子模块就有7个,一部分模块还有自己的子模块,包含了纯pom,jar的各种模块。
主要的子模块是 SpringBoot项目, 需要打包成一个可执行jar。

目标

WHAT 主要:使得项目易于构建部署,适应不同场景下的复杂工程的组织、发行、交付、落地中的问题。

HOW 方法:学习大型开源项目的打包方式(发行、组织),以及学习多种打包插件。

WHERE 应用场景实践:多场景部署,优化部署流程,减少不必要的构建和文件复制传输。

从现象寻找优化点

开发、测试环境中,经常改动的只有个别几个模块,有的时候甚至只有一个,需要一种能实现类似于增量编译、热部署的方式,加快流程速度。

已知有spring-boot-devtools可以在本地IDE开发环境中做到热部署,而实际上有的项目在本地无法完整启动,例如运维使用了防火墙限制了ZooKeeper的连接,数据库的连接。

比较影响开发体验。

于是把变化的和不变化的分开看待,分别打包部署,会灵活方便许多。

结构分析

封装格式

很多开源软件目录结构都是类似的,bin,lib,conf,plugin等等,这个可以通过maven-assembly-plugin实现,那么打包出来的是一个zip。

依赖

有三类,一种是其他模块编译出来的,通常会打包出 xxxx.jar,通过 dependency 像第三方依赖一样引入。
第二类是私服里面,其他项目,或者其他团队发行的 xxxx.jar。
第三类就是中央仓库的,第三方jar包。

分门别类

为了便于描述,假设该项目名字叫 slankka-application

依赖类型

压缩前的位置

期望的解压后路径

项目的脚本等

bin

/bin

启动类所在模块 (slankka-server)

slankka-server.jar

/

启动类所在模块(slankka-server)的资源、配置文件,例如application.yml

slankka-server/src/main/resources

/conf

其他子模块,例如 dao,common,core

slankka-dao/target/slankka-dao.jar,slankka-common/target/slankka-common.jar,slankka-core/target/slankka-core.jar

slankka-server.jar/

内部私服的依赖,例如其他项目的,slankka-rpc-provider

.m2

slankka-server.jar/META-INF/lib

SpringBoot配置文件

slankka-server.conf

/

依赖组织方式

如果只用 springboot-maven-plugin 打包,是比较常见的做法,这种方式打出来的jar 一般很大,当项目足够复杂,可能打出来的几百兆以上。

如果只用maven-jar-plugin,或者maven-dependency-plugin,则缺少了spring-boot-maven-plugin的优点。

解决办法是多种插件结合使用,在不同阶段使用不同的插件。

插件功能一览

插件

阶段

作用

组织范围

maven-jar-plugin

package

将资源和classes组合成jar

include,exclude到jar的文件,例如不需要的exclude,例如conf,logging

maven-shade-plugin

package

可以relocating类解决冲突,也可以将一些classes组装到一个jar内

这里可以把子模块的jar 装进springboot的jar

spring-boot-maven-plugin

package:repackage

将资源按照Spring自己的方式打包,设置layout等

打成可执行文件

maven-dependency-plugin

package:copy-dependencies

将scope=compile的jar复制到lib

复制所有依赖到lib

maven-assembly-plugin

package:single

最后一步,打包成一个ZIP包

将bin,conf,lib 等打包成一个ZIP

maven-enforcer-plugin

enforce:enforce

用于观察、限制项目中的环境,依赖版本以及传递依赖、依赖冲突

适合约束团队成员的,减少出错,开源项目使用的较多

应用细节

对于开发环境,maven-dependency-plugin,maven-assembly-plugin 构建过程,可以用 profile 做成可选:

例: 仅linux服务器需要打包成ZIP,Windows, Mac电脑不需要。

<activation>
        <activeByDefault>false</activeByDefault>
        <os>
          <name>linux</name>
          <family>unix</family>
          <arch>amd64</arch>
        </os>
</activation>

只有第一次打包上传到服务器时,才需要一个完整的ZIP。

快速迭代

对于日常开发,敏捷开发等,只需要前面三个插件。

(实践: 将原本200MB的jar (fat-jar或者ZIP)缩减成2MB左右。这样直接发布到服务器上,很快。)

3rd-party 和 子模块的依赖分开

对于子模块的迭代,单独打包(假如不用shade方式和springboot的jar放在一起),则会被maven-dependency-plugin复制到lib,和第三方jar混在一起,有时候会忘记替换,造成失误,不如按照半导体芯片领域模块化思维,直接集成到一个jar,那么就使用shade 把子模块的artifact压缩到一起,这样的好处就是,lib下,完全可以称之为 3rdParty,这部分几乎不会有变动。即便有的话,来一次完整的构建即可。

推荐使用shade插件将其他子模块和应用程序启动模块作为整体

shade的好处是,将子模块的字节码classes 和springboot应用所在模块放在同样的位置。
相比而言,spring-boot-maven-plugin 会放进 META-INF/lib内,这个非常适合其他项目的lib,作为整体嵌入这个jar。

弄清楚这些目标以后,就知道插件的include和exclude怎么写了。

插件负责的范围

插件

依赖类型

配置方法

spring-boot-maven-plugin

其他项目的依赖

configuration.includes

maven-dependency-plugin

第三方依赖

configuration.excludeArtifactIds 排除子模块、其他私服中的项目artifact

maven-shade-plugin

本项目的子模块

configuration.artifactSet.include

附录

以下配置,均在 slankka-server/pom.xml 内,最外层 slankka-application/pom.xml 是一个pom。

根据情况,有些放在build内,有些可放在 profile.build内。

maven-shade-plugin 的配置

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-shade-plugin</artifactId>
  <executions>
    <execution>
      <phase>package</phase>
      <goals>
        <goal>shade</goal>
      </goals>
      <configuration combine.children="append">
        <createDependencyReducedPom>false</createDependencyReducedPom>
        <artifactSet>
          <includes>
            <include>com.slankka.cloud:slankka-api</include>
            <include>com.slankka.cloud:slankka-cli</include>
            <include>com.slankka.cloud:slankka-common</include>
            <include>com.slankka.cloud:slankka-dao</include>
            <include>com.slankka.cloud:slankka-api</include>
          </includes>
        </artifactSet>
      </configuration>
    </execution>
  </executions>
</plugin>

spring-boot-maven-plugin的打包配置

<plugin>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-maven-plugin</artifactId>
  <version>${springboot.maven.plugin.version}</version>
  <executions>
    <execution>
      <goals>
        <goal>repackage</goal>
      </goals>
      <configuration>
        <attach>false</attach>
      </configuration>
    </execution>
  </executions>
  <configuration>
    <mainClass>com.slankka.server.Application</mainClass>
    <layout>ZIP</layout> <!--不记得为什么这么配,但压缩包ZIP是另外的插件实现-->
    <executable>true</executable> <!--非常好用,会注入bin/bash脚本到jar的头部,可以当shell脚本用-->
    <includes>
      <include>
        <groupId>com.slankka.rpc</groupId>
        <artifactId>rpc-provider</artifactId>
      </include>
      <include>
        <groupId>com.slankka.cloud</groupId>
        <artifactId>mikasa-cloud-api</artifactId>
      </include>
    </includes>
  </configuration>
</plugin>

如果没有要携带的其他项目依赖,则直接include里面写 none:none


maven-dependency-plugin的打包配置

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-dependency-plugin</artifactId>
  <version>${maven-dependency-plugin.version}</version>
  <executions>
    <execution>
      <id>copy-dependencies</id>
      <phase>package</phase>
      <goals>
        <goal>copy-dependencies</goal>
      </goals>
      <configuration>
        <excludeArtifactIds>
          slankka-api,slankka-cli,slankka-common,slankka-dao,slankka-rpc-provider,mikasa-cloud-api
        </excludeArtifactIds>
        <outputDirectory>target/lib</outputDirectory>
        <excludeTransitive>false</excludeTransitive>
        <stripVersion>false</stripVersion>
        <includeScope>runtime</includeScope>
      </configuration>
    </execution>
  </executions>
</plugin>

maven-assembly-plugin的配置

maven-jar-plugin 的配置