一、背景
我们使用Maven管理项目的时候,会遇到Jar瘦身的需求。例如打包后要将依赖的某些或全部Jar包单独输出到某个目录下方便查看,或要使项目本身的Jar尽可能的小方便传输等。本文介绍Jar瘦身的插件配置方法和一些在瘦身过程中遇到的问题及要点。在Jar瘦身过程中会使用到以下相关插件和脚本。
Maven打包插件:
- 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>
- 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>
- 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启动命令对于依赖的引用,有三种方式指定外部依赖路径:
- -Dloader.path 参数:
-Dloader.path=./libs
- -classpath 参数
-classpath=./libs
- 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
二、需求
- 在某些情况下, 我们需要将依赖的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>
- 由于将不同的容器依赖分不同目录打包,需要将相关目录都进行引用,也就需要修改我们的maven插件配置,和启动命令以达到【java启动时按照定义的目录加载我们需要的jar依赖包】
三、实操
- 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>
- POM文件中修改maven插件。使用spring-boot-maven-plugin实现zip打包控制。
- POM文件中修改maven插件。使用maven-jar-plugin实现jar包打包控制。
- 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>
- 编写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>
- 修改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
- 修改启动脚本/启动命令:增加-Dloader.path参数,参数值为依赖目录及当前选择的容器。
bash
"-Dloader.path=./libs,./libs/container/$(awk '/bsfit.server.type/ && !/#/ {print $2;exit}' bootstrap.yml)"
- 启动项目检查是否成功加载对应的容器依赖。
- 如果项目中有特殊使用了某个容器依赖包里的方法或类,需要根据项目和需求进行代码更新。
三、要点
- 如果要在java启动命令中使用-Dloader.path参数,必须使用spring-boot-maven-plugin插件,并且要指定configuration配置
- 尽量不要使用MANIFEST.MF文件指定classpath目录。MANIFEST.MF在pom文件中的maven-jar-plugin中指定生成。当指定了configuration.archive.manifest中的addClassPath为true时,则会在描述文件中生成指定的classpath列表并使我们启动命令中的-Dloader.path失效。其前缀为统一的classpathPrefix配置的前缀路径。所以此时很难再去单独指定各个容器的单独的目录。如果要指定则需要再增加manifestEntries相关配置,但是由于指定的路径为固定的,非常不利于后续版本的更新和修改。
- spring-boot-maven-plugin是无法修改jar包打包内的内容的,所以如果项目有需要控制jar包内打包内容,则需要结合maven-jar-plugin。