Maven项目打包生成瘦Jar(Jar瘦身)及指定依赖目录

一、背景

我们使用Maven管理项目的时候,会遇到Jar瘦身的需求。例如打包后要将依赖的某些或全部Jar包单独输出到某个目录下方便查看,或要使项目本身的Jar尽可能的小方便传输等。本文介绍Jar瘦身的插件配置方法和一些在瘦身过程中遇到的问题及要点。在Jar瘦身过程中会使用到以下相关插件和脚本。

Maven打包插件:

  1. jar包打包插件:spring-boot-maven-plugin
xml 复制代码
<plugin>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-maven-plugin</artifactId>
  <version>2.3.12.RELEASE</version>
  <executions>
    <execution>
      <goals>
        <goal>repackage</goal>
      </goals>
    </execution>
  </executions>
  <!-- 加入启动类-->
  <configuration>
    <layout>ZIP</layout>
    <mainClass>cn.com.bsfit.optimizer.DorApplication</mainClass>
    <includes>
      <include>
        <groupId>cn.com.bsfit</groupId>
        <artifactId>rule-mgr</artifactId>
      </include>
    </includes>
  </configuration>
</plugin>
  1. jar包打包插件:maven-jar-plugin
xml 复制代码
<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-jar-plugin</artifactId>
  <version>3.1.2</version>
  <configuration>
    <archive>
      <manifest>
        <addClasspath>true</addClasspath>
        <classpathPrefix>libs/</classpathPrefix>
        <mainClass>cn.com.bsfit.optimizer.DorApplication</mainClass>
      </manifest>
      <manifestEntries>
        <Class-Path>
          libs/container/tomcat/tomcat-annotations-api-9.0.60.jar libs/container/tomcat/tomcat-embed-core-9.0.60.jar libs/container/tomcat/tomcat-embed-el-9.0.60.jar
        </Class-Path>
      </manifestEntries>
    </archive>
    <excludes>
      <exclude>license/**</exclude>
      <exclude>bootstrap.yml</exclude>
      <exclude>application.yml</exclude>
      <exclude>application*.yml</exclude>
      <exclude>log4j2.xml</exclude>
    </excludes>
  </configuration>
</plugin>
  1. zip包打包插件:maven-assembly-plugin,使用assembly自定义zip打包内容。下文会给出assembly示例。
xml 复制代码
<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-assembly-plugin</artifactId>
  <version>3.1.1</version>
  <configuration>
    <finalName>rule-mgr-${project.version}</finalName>
    <appendAssemblyId>false</appendAssemblyId>
    <descriptors>
      <descriptor>package/assembly-${profile.assembly.package.suffix}.xml</descriptor>
    </descriptors>
  </configuration>
  <executions>
    <execution>
      <id>make-my-jar-with-dependencies</id>
      <phase>package</phase>
      <goals>
        <goal>single</goal>
      </goals>
    </execution>
  </executions>
</plugin>

瘦Jar打包

将Maven管理的Java项目依赖的Jar包从项目本身的Jar包中提出到其他外部目录,让jar包本身变轻量。通常我们会结合一个jar包打包插件和assembly打包插件实现Jar包瘦身。assembly可通过如下代码自定义打包路径:

xml 复制代码
<assembly>
  <!-- 设置打包Id -->
  <id>bin</id>
  <formats>
    <!-- 设置打包后的包格式 也可以是war tar.gz -->
    <format>zip</format>
  </formats>
  <!-- 打包的文件目录 -->
  <fileSets>
    <!-- 可以设置多个fileSet来制定多个打包输出的目录规则 -->
    <fileSet>
      <!-- 选择来源目录 这里是根目录 -->
      <directory>${project.basedir}/</directory>
      <!-- 选择输出目录 -->
      <outputDirectory>./doc</outputDirectory>
      <includes>
        <!-- 配置哪些文件需要打包 -->
        <include>README.md</include>
      </includes>
      <!-- 最后打包后目录结果就是 ./doc/README.dm -->
    </fileSet>
    <fileSet>
      <directory>${project.basedir}/package/</directory>
      <outputDirectory>./</outputDirectory>
      <filtered>true</filtered>
      <!-- 配置编码格式 -->
      <fileMode>0755</fileMode>
      <lineEnding>unix</lineEnding>
      <includes>
        <!-- 可以使用通配符 -->
        <include>*.sh</include>
      </includes>
    </fileSet>
    <fileSet>
      <directory>${project.basedir}/package/</directory>
      <outputDirectory>./</outputDirectory>
      <includes>
        <include>doc/**</include>
        <include>init/configData/*</include>
        <!-- 开发环境和生产环境的配置不同 -->
        <include>application.yml</include>
        <include>sql/*/**</include>
      </includes>
    </fileSet>

    <fileSet>
      <directory>${project.basedir}/src/main/resources/</directory>
      <outputDirectory>./</outputDirectory>
      <includes>
        <include>config/**</include>
        <!-- 不要把license打进去 -->
        <!-- <include>license/license.dat</include>-->
        <include>log4j2.xml</include>
        <include>spark-log4j2.xml</include>
        <include>PARAMS.YML</include>
        <include>security_config.xml</include>
        <include>bootstrap.yml</include>
      </includes>
    </fileSet>

    <fileSet>
      <directory>${project.basedir}/src/main/resources/license</directory>
      <outputDirectory>./</outputDirectory>
      <includes>
        <!--                <include>license.dat</include>-->
      </includes>
    </fileSet>

    <fileSet>
      <directory>${project.build.directory}/</directory>
      <outputDirectory>./</outputDirectory>
      <includes>
        <!-- 这里的jar包是maven打的项目jar包,是瘦jar -->
        <include>*.jar</include>
      </includes>
    </fileSet>
  </fileSets>

	<dependencySets>
    <!-- 依赖打包控制 -->
  	<dependencySet>
      <!-- 依赖打包到的目录 -->
      <outputDirectory>libs</outputDirectory>
      <useProjectArtifact>true</useProjectArtifact>
      <excludes>
        <exclude>cn.com.bsfit:rule-mgr</exclude>
        <exclude>cn.com.bsfit:bsfit-spark-decision:zip</exclude>
      </excludes>
    </dependencySet>
  </dependencySets>
</assembly>

java启动命令(启动脚本)

java启动命令对于依赖的引用,有三种方式指定外部依赖路径:

  1. -Dloader.path 参数:

-Dloader.path=./libs

  1. -classpath 参数

-classpath=./libs

  1. MANIFEST.MF 描述文件中指定classpath
bash 复制代码
Manifest-Version: 1.0
Start-Class: cn.com.bsfit.cooperation.center.CooperationCenterApplicat
 ion
Spring-Boot-Classes: BOOT-INF/classes/
Class-Path: libs/tongweb-spring-boot-starter-2.x.0.RELEASE.jar libs/to
 ngweb-embed-7.0.E.2_P1.jar libs/oceanbase-client-2.2.11.jar libs/ops-
 component-metrics-1.0.0-1.0.jar libs/spring-boot-starter-actuator-2.7
 .2.jar

二、需求

  1. 在某些情况下, 我们需要将依赖的jar包分成不同的目录进行打包,如分成公共目录和项目自己的目录,或者某些jar包要根据不同的环境、场景进行依赖使用,防止jar包冲突的问题。此时就需要在assmebly依赖打包配置中配置多个目录 。例如当前需求:因为兼容多类型容器(Tomcat、Tongweb、bes)的需求,项目引入了三类容器的相关依赖,但是依赖可能在不同环境下的使用场景不同,也可能因为容器相关引用内容产生依赖冲突,所以我们希望将容器相关依赖jar包隔离开:
xml 复制代码
<dependencySets>
  <dependencySet>
      <outputDirectory>libs</outputDirectory>
      <useProjectArtifact>true</useProjectArtifact>
      <excludes>
          <exclude>cn.com.bsfit:rule-mgr</exclude>
          <exclude>cn.com.bsfit:bsfit-spark-decision:zip</exclude>
          <exclude>org.apache.tomcat.embed:tomcat-embed-core</exclude>
          <exclude>org.apache.tomcat.embed:tomcat-embed-el</exclude>
          <exclude>org.apache.tomcat:tomcat-annotations-api</exclude>
          <exclude>com.tongweb:tongweb-embed</exclude>
          <exclude>com.tongweb.springboot:tongweb-spring-boot-starter</exclude>
          <exclude>com.bes.appserver:bes-gmssl</exclude>
          <exclude>com.bes.appserver:bes-lite-spring-boot-2.x-starter</exclude>
          <exclude>com.bes.appserver:bes-websocket</exclude>
      </excludes>
  </dependencySet>

  <dependencySet>
      <includes>
          <include>cn.com.bsfit:bsfit-spark-decision:zip</include>
      </includes>
      <outputDirectory>sparklib</outputDirectory>
      <useProjectArtifact>false</useProjectArtifact>
  </dependencySet>
  <!-- 容器相关依赖单独打包 -->
  <dependencySet>
    <includes>
      <include>org.apache.tomcat.embed:tomcat-embed-core</include>
      <include>org.apache.tomcat.embed:tomcat-embed-el</include>
      <include>org.apache.tomcat:tomcat-annotations-api</include>
    </includes>
    <!--不使用项目的artifact,第三方jar不要解压,打包进zip文件的lib目录-->
    <useProjectArtifact>false</useProjectArtifact>
    <outputDirectory>libs/container/tomcat</outputDirectory>
    <unpack>false</unpack>
  </dependencySet>
  <dependencySet>
    <includes>
      <include>com.tongweb:tongweb-embed</include>
      <include>com.tongweb.springboot:tongweb-spring-boot-starter</include>
    </includes>
    <!--不使用项目的artifact,第三方jar不要解压,打包进zip文件的lib目录-->
    <useProjectArtifact>false</useProjectArtifact>
    <outputDirectory>libs/container/tongweb</outputDirectory>
    <unpack>false</unpack>
  </dependencySet>
  <dependencySet>
    <includes>
      <include>com.bes.appserver:bes-gmssl</include>
      <include>com.bes.appserver:bes-lite-spring-boot-2.x-starter</include>
      <include>com.bes.appserver:bes-websocket</include>
    </includes>
    <!--不使用项目的artifact,第三方jar不要解压,打包进zip文件的lib目录-->
    <useProjectArtifact>false</useProjectArtifact>
    <outputDirectory>libs/container/bes</outputDirectory>
    <unpack>false</unpack>
  </dependencySet>
</dependencySets>
  1. 由于将不同的容器依赖分不同目录打包,需要将相关目录都进行引用,也就需要修改我们的maven插件配置,和启动命令以达到【java启动时按照定义的目录加载我们需要的jar依赖包】

三、实操

  1. POM文件先引入各容器的依赖。公司项目可直接引用最新的bsfit-util-all相关依赖。
xml 复制代码
<properties>
	<bsfit-utils.version>1.8.7</bsfit-utils.version>
</properties>

<dependencyManagement>
  <dependencies>
    <dependency>
        <groupId>cn.com.bsfit</groupId>
        <artifactId>bsfit-utils-web</artifactId>
        <version>${bsfit-utils.version}</version>
    </dependency>
  </dependencies>
</dependencyManagement>
  1. POM文件中修改maven插件。使用spring-boot-maven-plugin实现zip打包控制。
  2. POM文件中修改maven插件。使用maven-jar-plugin实现jar包打包控制。
  3. POM文件中修改maven插件。使用maven-assembly-plugin自定义zip包打包内容。
xml 复制代码
<build>
  <plugins>
    <!-- zip 包 -->
    <plugin>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-maven-plugin</artifactId>
      <version>2.3.12.RELEASE</version>
      <executions>
        <execution>
          <goals>
            <goal>repackage</goal>
          </goals>
        </execution>
      </executions>
      <!-- 加入启动类-->
      <configuration>
        <!-- 这个不可遗漏 -->
        <layout>ZIP</layout>
        <mainClass>cn.com.bsfit.optimizer.DorApplication</mainClass>
        <includes>
          <!-- 使用自己的项目信息 -->
          <include>
            <groupId>cn.com.bsfit</groupId>
            <artifactId>rule-mgr</artifactId>
          </include>
        </includes>
      </configuration>
    </plugin>
    <!-- jar包 -->
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-jar-plugin</artifactId>
      <version>3.1.2</version>
      <configuration>
        <!-- 控制生成的jar包要排除哪些项目文件 -->
        <excludes>
          <exclude>license/**</exclude>
          <exclude>bootstrap.yml</exclude>
          <exclude>application.yml</exclude>
          <exclude>application*.yml</exclude>
          <exclude>log4j2.xml</exclude>
        </excludes>
      </configuration>
    </plugin>
    <!-- assmebly -->
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-assembly-plugin</artifactId>
      <version>3.1.1</version>
      <configuration>
        <!-- 使用自己的项目信息 -->
        <finalName>rule-mgr-${project.version}</finalName>
        <appendAssemblyId>false</appendAssemblyId>
        <descriptors>
          <!-- 定义assembly配置文件路径,可利用后缀名来进行多配置文件的快速替换 -->
          <descriptor>package/assembly-${profile.assembly.package.suffix}.xml</descriptor>
        </descriptors>
      </configuration>
      <executions>
        <execution>
          <id>make-my-jar-with-dependencies</id>
          <phase>package</phase>
          <goals>
            <goal>single</goal>
          </goals>
        </execution>
      </executions>
    </plugin>
	</plugins>
</build>
  1. 编写assembly.xml配置文件,完成不同目录的jar包打包输出配置。
xml 复制代码
<assembly>
  <id>bin</id>
  <formats>
    <format>zip</format>
  </formats>
  <fileSets>
    <fileSet>
      <directory>${project.basedir}/</directory>
      <outputDirectory>./doc</outputDirectory>
      <includes>
        <include>README.md</include>
      </includes>
    </fileSet>
    <fileSet>
      <directory>${project.basedir}/package/</directory>
      <outputDirectory>./</outputDirectory>
      <filtered>true</filtered>
      <fileMode>0755</fileMode>
      <lineEnding>unix</lineEnding>
      <includes>
        <include>*.sh</include>
      </includes>
    </fileSet>
    <fileSet>
      <directory>${project.basedir}/package/</directory>
      <outputDirectory>./</outputDirectory>
      <includes>
        <include>doc/**</include>
        <include>init/configData/*</include>
        <!-- 开发环境和生产环境的配置不同 -->
        <include>application.yml</include>
        <include>sql/*/**</include>
      </includes>
    </fileSet>

    <fileSet>
      <directory>${project.basedir}/src/main/resources/</directory>
      <outputDirectory>./</outputDirectory>
      <includes>
        <include>config/**</include>
        <!--                <include>license/license.dat</include>-->
        <include>log4j2.xml</include>
        <include>spark-log4j2.xml</include>
        <include>PARAMS.YML</include>
        <include>security_config.xml</include>
        <include>bootstrap.yml</include>
      </includes>
    </fileSet>

    <fileSet>
      <directory>${project.basedir}/src/main/resources/license</directory>
      <outputDirectory>./</outputDirectory>
      <includes>
        <!--                <include>license.dat</include>-->
      </includes>
    </fileSet>

    <fileSet>
      <directory>${project.build.directory}/</directory>
      <outputDirectory>./</outputDirectory>
      <includes>
        <include>*.jar</include>
      </includes>
    </fileSet>
  </fileSets>

  <dependencySets>
    <dependencySet>
      <!-- 指定依赖输出目录 -->
      <outputDirectory>libs</outputDirectory>
      <useProjectArtifact>true</useProjectArtifact>
      <excludes>
        <!-- 将容器相关依赖排除 后面重新指定到对应的容器目录下 -->
        <exclude>cn.com.bsfit:rule-mgr</exclude>
        <exclude>cn.com.bsfit:bsfit-spark-decision:zip</exclude>
        <exclude>org.apache.tomcat.embed:tomcat-embed-core</exclude>
        <exclude>org.apache.tomcat.embed:tomcat-embed-el</exclude>
        <exclude>org.apache.tomcat:tomcat-annotations-api</exclude>
        <exclude>com.tongweb:tongweb-embed</exclude>
        <exclude>com.tongweb.springboot:tongweb-spring-boot-starter</exclude>
        <exclude>com.bes.appserver:bes-gmssl</exclude>
        <exclude>com.bes.appserver:bes-lite-spring-boot-2.x-starter</exclude>
        <exclude>com.bes.appserver:bes-websocket</exclude>
      </excludes>
    </dependencySet>

    <dependencySet>
      <includes>
        <include>cn.com.bsfit:bsfit-spark-decision:zip</include>
      </includes>
      <outputDirectory>sparklib</outputDirectory>
      <useProjectArtifact>false</useProjectArtifact>
    </dependencySet>

    <!-- 容器相关依赖单独打包 -->
    <dependencySet>
      <includes>
        <include>org.apache.tomcat.embed:tomcat-embed-core</include>
        <include>org.apache.tomcat.embed:tomcat-embed-el</include>
        <include>org.apache.tomcat:tomcat-annotations-api</include>
      </includes>
      <!--不使用项目的artifact,第三方jar不要解压,打包进zip文件的lib目录-->
      <useProjectArtifact>false</useProjectArtifact>
      <outputDirectory>libs/container/tomcat</outputDirectory>
      <unpack>false</unpack>
    </dependencySet>
    <dependencySet>
      <includes>
        <include>com.tongweb:tongweb-embed</include>
        <include>com.tongweb.springboot:tongweb-spring-boot-starter</include>
      </includes>
      <!--不使用项目的artifact,第三方jar不要解压,打包进zip文件的lib目录-->
      <useProjectArtifact>false</useProjectArtifact>
      <outputDirectory>libs/container/tongweb</outputDirectory>
      <unpack>false</unpack>
    </dependencySet>
    <dependencySet>
      <includes>
        <include>com.bes.appserver:bes-gmssl</include>
        <include>com.bes.appserver:bes-lite-spring-boot-2.x-starter</include>
        <include>com.bes.appserver:bes-websocket</include>
      </includes>
      <!--不使用项目的artifact,第三方jar不要解压,打包进zip文件的lib目录-->
      <useProjectArtifact>false</useProjectArtifact>
      <outputDirectory>libs/container/bes</outputDirectory>
      <unpack>false</unpack>
    </dependencySet>
	</dependencySets>
</assembly>
  1. 修改bootstrap.yml项目启动配置文件,增加一个当前使用容器配置项。
shell 复制代码
#web容器相关配置
##tomcat(默认)
bsfit.server.type: tomcat
##tongweb
#bsfit.server.type:tongweb
##tongweb配置
#server.tongweb.license:
#type:file
#path:license.dat
##宝兰德
#bsfit.server.type:bes
##宝兰德license目录配置
#server.bes.basedir:bes
  1. 修改启动脚本/启动命令:增加-Dloader.path参数,参数值为依赖目录及当前选择的容器。
bash 复制代码
"-Dloader.path=./libs,./libs/container/$(awk '/bsfit.server.type/ && !/#/ {print $2;exit}' bootstrap.yml)"
  1. 启动项目检查是否成功加载对应的容器依赖。
  2. 如果项目中有特殊使用了某个容器依赖包里的方法或类,需要根据项目和需求进行代码更新。

三、要点

  1. 如果要在java启动命令中使用-Dloader.path参数,必须使用spring-boot-maven-plugin插件,并且要指定configuration配置
  2. 尽量不要使用MANIFEST.MF文件指定classpath目录。MANIFEST.MF在pom文件中的maven-jar-plugin中指定生成。当指定了configuration.archive.manifest中的addClassPath为true时,则会在描述文件中生成指定的classpath列表并使我们启动命令中的-Dloader.path失效。其前缀为统一的classpathPrefix配置的前缀路径。所以此时很难再去单独指定各个容器的单独的目录。如果要指定则需要再增加manifestEntries相关配置,但是由于指定的路径为固定的,非常不利于后续版本的更新和修改。
  1. spring-boot-maven-plugin是无法修改jar包打包内的内容的,所以如果项目有需要控制jar包内打包内容,则需要结合maven-jar-plugin。
相关推荐
尘浮生7 小时前
Java项目实战II基于Spring Boot的光影视频平台(开发文档+数据库+源码)
java·开发语言·数据库·spring boot·后端·maven·intellij-idea
aloha_78915 小时前
从零记录搭建一个干净的mybatis环境
java·笔记·spring·spring cloud·maven·mybatis·springboot
尢词17 小时前
SpringMVC
java·spring·java-ee·tomcat·maven
wrx繁星点点17 小时前
享元模式:高效管理共享对象的设计模式
java·开发语言·spring·设计模式·maven·intellij-idea·享元模式
前 方19 小时前
若依入门案例
java·spring boot·maven
咕哧普拉啦19 小时前
乐尚代驾十订单支付seata、rabbitmq异步消息、redisson延迟队列
java·spring boot·mysql·spring·maven·乐尚代驾·java最新项目
不像程序员的程序媛21 小时前
mybatisgenerator生成mapper时报错
maven·mybatis
移民找老国21 小时前
加拿大移民新风向
java-ee·maven·phpstorm·visual studio code·nio
LUwantAC21 小时前
Java学习路线:Maven(三)继承关系
java·学习·maven
尘浮生1 天前
Java项目实战II基于Spring Boot的问卷调查系统的设计与实现(开发文档+数据库+源码)
java·开发语言·数据库·spring boot·后端·maven·intellij-idea