目录
- 正文
- 本次用到的插件
- 小步慢跑实现:jar包瘦身,lib包外置
- 准备工作:
- 实践的基本步骤
- 先看成果最终信息
- 业务 jar 的位置操作
- 总结
本篇文章为自己亲自实践所得,项目是基于 spring boot 的多模块 Maven 项目, 依赖错综复杂。参考网上千篇一律的复制文章躺了不少坑。
整体感觉下来,Maven就是一把利剑,理解的到位,能化腐朽为神奇,基础不牢,费心费神。为了减小本文篇幅,基础知识一定要参考apache maven 官网学习
为了少走弯路,基础插件知识请进入官网学习:https://maven.apache.org/plugins/index.html
正文
乍一看,Maven 可能看起来有很多东西,但简而言之,maven是将项目工程模式应用于项目构建的基础架构。
讲人话就是:Maven是一个插件执行框架;所有工作都由插件完成。同时 Maven 基于构建生命周期的核心概念,明确定义了构建和分发特定工件(项目)的过程。
本次用到的插件
非官方插件
插件 | 作用 |
spring-boot-maven-plugin | spring boot官方打包插件,本次使用其 layout 为 ZIP 的模式进行打包 |
官方插件:
插件 | 作用 |
spring-boot-maven-plugin | spring boot官方打包插件,本次使用其 layout 为 ZIP 的模式进行打包 |
官方插件:
插件 | 作用 |
maven-resources-plugin | 资源文件处理插件,配置文件中的 @…@ 包围的变量 |
maven-dependency-plugin | 依赖操作(复制、解包)和分析。本次用来将依赖的 jar 包移到lib文件夹中 |
maven-assembly-plugin | 构建源和/或二进制文件的程序包(分发)。比如打包成 zip 文件 |
maven插件有着自己的一套命名规范。官方插件命名的格式为 [maven-xxx
-plugin],非官方的插件命名为 [xxx
-maven-plugin] 。是不是觉得很眼熟,没错,spring boot starter 的命名也有类似的规范。
如下几个插件可以看其名字就能区分是maven官方的还是非官方的:spring-boot-maven-plugin、maven-surefire-plugin、maven-resources-plugin
官方插件可以详见官方地址:https://maven.apache.org/plugins/index.html
小步慢跑实现:jar包瘦身,lib包外置
千万不要想着一步实现lib包外置,我们的目标是将lib包外置,网上一堆文章直接贴整体配置文档,注释和当时什么地方有坑言之较少,所以我们还是得按自己的想法进行尝试,确保问题控制在自己的掌控范围之内。
目标: 实现lib外置
准备工作:
- 打包fast jar ,因为使用的是 spring boot项目,使用官方提供的插件进行打包。
- 使用 spring-boot-maven-plugin 插件,layout加载方式改为 ZIP 模式可以实现。(因为该插件是在maven-jar-plugin 插件之后执行)
使用官方的 maven-jar-plugin(我尝试过但是依赖导入会有问题总是报错“java.lang.ArrayStoreException: sun.reflect.annotation.TypeNotPresentExceptionProxy”类型的错误,因为MANIFEST.MF文件中Class-Path参数不能个性化排除业务jar。所以我调试无果之后仅使用的是spring boot的官方插件)- 有哪些方法可以实现 fast jar中的lib文件中的jar包移到jar外部的文件夹中
- 需要使用 maven-dependency-plugin 插件
- fast jar的内部结构了解,了解打包插件最终修改的是哪些参数。
MANIFEST.MF
文件及其参数了解, BOOT-INF文件夹作用 - pom文件中
build
配置的标签有哪些自己不知道的,比如 configuration 参数如何查询(官方文档) - spring-boot-maven-plugin 插件能实现哪些功能,phase、goals
- 经了解spring-boot-maven-plugin 实现lib 外置需要,切换打包方式
<layout>ZIP</layout>
启用-Dloader.path=lib/
- 使用 includes 标签可以将匹配的 jar 保留在原来的BOOT-INF/lib中
实践的基本步骤
- 切换layout 为 ZIP模式,确保项目启动成功
- 先将lib包全部外置,确保项目再次启动成功。(需要使用-Dloader.path=lib/ 启动参数)
- 过滤业务相关的jar,打包到 fast jar 中,启动肯定是要报错,错误来源与外部lib的jar包
- 配置maven-dependency-plugin 排除业务jar放入外置lib文件夹中。
先看成果最终信息
启动的时候需要增加 -Dloader.path=lib/
参数,以下只是一个简单的启动命令:
java -jar -Dloader.path=lib/ gd_mbs_zfxn.jar
文件结构,jar包因为了便于展示结构,已解压成文件
. 本文件夹
├─gd_mbs_zfxn.jar ## jar 包
│ ├─BOOT-INF
│ │ ├─classes
│ │ │ ├─com
│ │ │ │ └─**** ## 省略详细信息,主要包含的启动类的class文件,对应MANIFEST.MF 文件的 Start-Class项
│ │ │ ├─config ## 存放配置文件 yml,xml 等等
│ │ │ ├─templates
│ │ │ │ └─license
│ │ │ └─xml
│ │ └─lib ## 还有项目依赖的业务 jar 包
│ ├─META-INF
│ │ └─maven
│ │ └─com.bdsoft.framework.gd
│ │ └─leea-app
│ └─org
│ └─springframework
│ └─boot
│ └─loader
│ ├─archive
│ ├─data
│ ├─jar
│ └─util
└─lib
1.所得jar包中: MANIFEST.MF 文件内容如下,请注意 Main-Class是PropertiesLauncher
,这个是pom中 spring-boot-maven-plugin 插件配置 <layout>ZIP</layout>
的结果。
Manifest-Version: 1.0 | |
Implementation-Title: APP启动配置 | |
Implementation-Version: 1.0.0.0 | |
Start-Class: com.bdsoft.BDPWebApplication | |
Spring-Boot-Classes: BOOT-INF/classes/ | |
Spring-Boot-Lib: BOOT-INF/lib/ | |
Build-Jdk-Spec: 1.8 | |
Spring-Boot-Version: 2.2.7.RELEASE | |
Created-By: Maven Jar Plugin 3.2.0 | |
Main-Class: org.springframework.boot.loader.PropertiesLauncher |
2.配置文件不外置,仍然放在jar的原来位置 BOOT-INF/classes 文件下面
这个需要
3.业务jar仍然放在jar内部的 BOOT-INF/lib 文件下.
4.非业务的其他依赖jar包(不包含3中的三个业务jar)全部放置在启动jar(fast jar)同级的 lib文件夹下面。
全部 pom配置
<build> | |
<finalName>gd_mbs_zfxn</finalName> | |
<!-- mavne打包动态修改替换配置文件中的占位符 --> | |
<resources> | |
<resource> | |
<directory>${basedir}/src/main/resources</directory> | |
<!-- 处理其中的可替换参数(@..@符合标注的变量) --> | |
<filtering>true</filtering> | |
</resource> | |
<!-- 可以配置引入哪些配置文件includes或排除哪些文件excludes --> | |
</resources> | |
<plugins> | |
<!-- spring boot的打包插件进行打包成 fastjar --> | |
<plugin> | |
<groupId>org.springframework.boot</groupId> | |
<artifactId>spring-boot-maven-plugin</artifactId> | |
<version>2.2.7.RELEASE</version> | |
<configuration> | |
<!-- --> | |
<layout>ZIP</layout> | |
<includeSystemScope>true</includeSystemScope> | |
<mainClass>com.bdsoft.BDPWebApplication</mainClass> | |
<includes> | |
<!-- 项目启动jar包中保留依赖包 --> | |
<include> | |
<groupId>com.bdsoft.framework.gd</groupId> | |
<artifactId>leea-sym</artifactId> | |
</include> | |
<include> | |
<groupId>com.bdsoft.framework.gd</groupId> | |
<artifactId>leea-efficiency</artifactId> | |
</include> | |
<include> | |
<groupId>com.googlecode.aviator</groupId> | |
<artifactId>leea-aviator</artifactId> | |
</include> | |
</includes> | |
</configuration> | |
<executions> | |
<execution> | |
<goals> | |
<!-- 这个是默认 goal,在 mvn package 执行之后,这个命令再次打包生成可执行的 jar,同时将 mvn package 生成的 jar 重命名为 *.origin --> | |
<goal>repackage</goal> | |
</goals> | |
</execution> | |
</executions> | |
</plugin> | |
<!--打包时跳过测试代码--> | |
<plugin> | |
<groupId>org.apache.maven.plugins</groupId> | |
<artifactId>maven-surefire-plugin</artifactId> | |
<version>2.22.2</version> | |
<configuration> | |
<skip>true</skip> | |
</configuration> | |
</plugin> | |
<!-- 配置文件处理插件 --> | |
<plugin> | |
<groupId>org.apache.maven.plugins</groupId> | |
<artifactId>maven-resources-plugin</artifactId> | |
<version>3.1.0</version> | |
<configuration> | |
<encoding>utf-8</encoding> | |
<!-- 是否使用默认占位符@@ --> | |
<useDefaultDelimiters>true</useDefaultDelimiters> | |
</configuration> | |
</plugin> | |
<plugin> | |
<groupId>org.apache.maven.plugins</groupId> | |
<artifactId>maven-dependency-plugin</artifactId> | |
<version>3.1.0</version> | |
<executions> | |
<!--导出所有的 jar 包--> | |
<execution> | |
<id>pra1</id> | |
<!-- (打包阶段):在实际打包中,执行任何的必要的操作。 --> | |
<phase>package</phase> | |
<goals> | |
<goal>copy-dependencies</goal> | |
</goals> | |
<configuration> | |
<outputDirectory>${project.build.directory}/lib</outputDirectory> | |
<stripVersion>false</stripVersion> | |
<!-- 不包含哪些jar包 --> | |
<excludeGroupIds> | |
<!-- 只排除业务模块相关的jar包,多个用英文逗号分割--> | |
com.bdsoft.framework.gd, | |
com.googlecode.aviator | |
</excludeGroupIds> | |
</configuration> | |
</execution> | |
</executions> | |
</plugin> | |
<!-- 打包zip插件 --> | |
<plugin> | |
<artifactId>maven-assembly-plugin</artifactId> | |
<version>3.4.2</version> | |
<configuration> | |
<descriptors> | |
<descriptor>src/main/assembly/assembly.xml</descriptor> | |
</descriptors> | |
</configuration> | |
<executions> | |
<execution> | |
<id>make-assembly</id> | |
<phase>package</phase> | |
<goals> | |
<goal>single</goal> | |
</goals> | |
</execution> | |
</executions> | |
</plugin> | |
</plugins> | |
</build> |
maven-assembly-plugin 插件对应的 assembly.xml 内容如下
<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.1.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |
xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.1.1 https://maven.apache.org/xsd/assembly-2.1.1.xsd"> | |
<!-- 可自定义,这里指定的是项目环境 --> | |
<!-- gd_mbs_zfxn-dev-1.0.0.0.tar.gz --> | |
<id>${profiles.active}-${project.version}</id> | |
<!-- 打包的类型,如果有N个,将会打N个类型的包 --> | |
<formats> | |
<format>tar.gz</format> | |
<format>zip</format> | |
</formats> | |
<!-- 默认为true。指定打的包是否包含打包层目录(比如finalName是prefix,当值为true,所有文件被放在包内的prefix目录下, | |
否则直接放在包的根目录下。finalName标签在pom文件中注册插件里面的configuration标签下 --> | |
<includeBaseDirectory>false</includeBaseDirectory> | |
<!-- 指定要包含在程序集中的文件组。通过提供一个或多个 <fileSet> 子元素来指定文件集。 --> | |
<fileSets> | |
<!-- fileMode,还有一个 directoryMode | |
与 UNIX 权限类似,设置所包含文件的文件模式。这是一个八进制值。格式:(User)(Group)(Other) 其中每个组件是 Read = 4、Write = 2 和 Execute = 1 的总和。例如,值 0644 转换为 User read-write、Group and Other read-only . 默认值为 0644 | |
0755->即用户具有读/写/执行权限,组用户和其它用户具有读写权限; | |
0644->即用户具有读写权限,组用户和其它用户具有只读权限; | |
--> | |
<!-- 将src/bin目录下的所有文件输出到打包后的bin目录中 --> | |
<fileSet> | |
<directory>${basedir}/src/bin</directory> | |
<outputDirectory>bin</outputDirectory> | |
<fileMode>0755</fileMode> | |
<includes> | |
<include>**.sh</include> | |
<include>**.bat</include> | |
</includes> | |
</fileSet> | |
<!-- 指定输出target/classes中的配置文件到config目录中 --> | |
<fileSet> | |
<directory>${project.build.directory}/classes</directory> | |
<outputDirectory>config</outputDirectory> | |
<fileMode>0644</fileMode> | |
<includes> | |
<!-- 启动必须的的 bootstrap文件 --> | |
<include>**/bootstrap.yml</include> | |
<!-- 主要配置文件、system配置文件和对应环境${profiles.active}的文件 --> | |
<include>**/application.yml</include> | |
<include>**/application-system.yml</include> | |
<include>**/application-${profiles.active}.yml</include> | |
<!-- mybatis文件 暂时冗余 --> | |
<include>mapper/**/*.xml</include> | |
<!-- 静态文件 暂时冗余 --> | |
<include>static/**</include> | |
<!-- 模板文件 --> | |
<include>templates/**</include> | |
<!-- 全部的xml文件 --> | |
<include>**/*.xml</include> | |
<!-- 全部的配置文件 --> | |
<include>**/*.properties</include> | |
<include>**/*.txt</include> | |
</includes> | |
</fileSet> | |
<!-- 将第三方依赖打包到lib目录中 --> | |
<fileSet> | |
<directory>${basedir}/target/lib</directory> | |
<outputDirectory>lib</outputDirectory> | |
<fileMode>0755</fileMode> | |
</fileSet> | |
<!-- 将项目启动jar打包到boot目录中,target文件夹下 --> | |
<fileSet> | |
<directory>${project.build.directory}</directory> | |
<outputDirectory></outputDirectory> | |
<fileMode>0755</fileMode> | |
<includes> | |
<include>${project.build.finalName}.jar</include> | |
</includes> | |
</fileSet> | |
<!-- 包含根目录下的文件 --> | |
<fileSet> | |
<directory>${basedir}</directory> | |
<includes> | |
<include>*.md</include> | |
</includes> | |
</fileSet> | |
</fileSets> | |
</assembly> |
上的${profiles.active}
变量来源于下面的profiles设置,如果不需要动态配置文件,可以忽略将上面相关变量${profiles.active}
删除。
<profiles> | |
<profile> | |
<!-- 生产环境 --> | |
<id>pross</id> | |
<properties> | |
<!-- 激活的环境 --> | |
<profiles.active>pro</profiles.active> | |
<!-- 其他参数的配置,编译打包的时候替换资源文件中的 @testparam@ 变量 --> | |
<testparam>xxx</testparam> | |
</properties> | |
</profile> | |
<profile> | |
<!-- 预发布环境 --> | |
<id>prepro</id> | |
<properties> | |
<!-- 激活的环境文件 --> | |
<profiles.active>prepro</profiles.active> | |
<!-- 其他参数的配置,编译打包的时候替换资源文件中的 @testparam@ 变量 --> | |
<testparam>xxx</testparam> | |
</properties> | |
</profile> | |
<profile> | |
<!-- 本地开发环境 --> | |
<id>dev</id> | |
<properties> | |
<!-- 激活的环境文件 --> | |
<profiles.active>dev</profiles.active> | |
<!-- 其他参数的配置,编译打包的时候替换资源文件中的 @testparam@ 变量 --> | |
<testparam>xxx</testparam> | |
</properties> | |
<activation> | |
<!-- 默认选中本地环境 --> | |
<activeByDefault>true</activeByDefault> | |
</activation> | |
</profile> | |
<profile> | |
<!-- 测试环境 --> | |
<id>test</id> | |
<properties> | |
<!-- 激活的环境文件 --> | |
<profiles.active>test</profiles.active> | |
<!-- 其他参数的配置,编译打包的时候替换资源文件中的 @testparam@ 变量 --> | |
<testparam>xxx</testparam> | |
</properties> | |
</profile> | |
</profiles> |
业务 jar 的位置操作
想要实现业务 jar 仍然放在jar内部的 BOOT-INF/lib 文件下,其他依赖 jar 放入 lib文件夹中,需要两个插件配合。maven-dependency-plugin 和 spring-boot-maven-plugin 插件在 package 阶段根据各自的匹配策略将 业务jar 排除和匹配放入
核心思路就是:
- 我们想实现的目的是什么,这个目的处在maven生命周期的什么阶段,对应使用什么插件(<plugin>)
- 我们的目的需要在什么触发事件中执行(<execution><phase>)
- 我们的目的需要什么组件去实现,是测试组件还是打包组件还是依赖组件。将我们的想法分解成符合maven的工程概念,并找到其中合适的组件,配置它提供的<goals><goal>(对应Mojo)目标功能(dependency)实现我们的目的。让它操作基于我们提供的匹配原则(includes符合或excludes排除)符合的文件<configuration>
核心注意点:
- jar 包是在package阶段,我们需要将依赖jar导出到lib包中,可以使用为了便于理解我都采用的是 package阶段
- spring-boot-maven-plugin 插件中仅放行 业务jar 打入fast jar,请看
<include>
标签,因为默认 goal 是repackage,对应的是mvn package,maven-dependency-plugin 插件也使用 package 阶段,该插件使用详见:https://docs.spring.io/spring-boot/docs/2.2.1.RELEASE/maven-plugin/
<includes> | |
<!-- 项目启动jar包中保留依赖包 --> | |
<include> | |
<groupId>com.bdsoft.framework.gd</groupId> | |
<artifactId>leea-sym</artifactId> | |
</include> | |
<include> | |
<groupId>com.bdsoft.framework.gd</groupId> | |
<artifactId>leea-efficiency</artifactId> | |
</include> | |
<include> | |
<groupId>com.googlecode.aviator</groupId> | |
<artifactId>leea-aviator</artifactId> | |
</include> | |
</includes> |
3.maven-dependency-plugin插件中不能再将 业务jar 输出到外置 lib 文件夹,所以需要排除相应的业务jar,不然会在启动的时候报创建bean失败的错误,详见 excludeGroupIds
标签,记得使用 英文逗号分割,该插件使用详见:https://maven.apache.org/plugins/maven-dependency-plugin/,排除依赖相关说明详见copy-dependencies的文档:https://maven.apache.org/plugins/maven-dependency-plugin/copy-dependencies-mojo.html
<plugin> | |
<groupId>org.apache.maven.plugins</groupId> | |
<artifactId>maven-dependency-plugin</artifactId> | |
<version>3.1.0</version> | |
<executions> | |
<!--导出所有的 jar 包--> | |
<execution> | |
<id>pra1</id> | |
<!-- (打包阶段):在实际打包中,执行任何的必要的操作。 --> | |
<phase>package</phase> | |
<goals> | |
<goal>copy-dependencies</goal> | |
</goals> | |
<configuration> | |
<outputDirectory>${project.build.directory}/lib</outputDirectory> | |
<stripVersion>false</stripVersion> | |
<!-- 不包含哪些jar包 --> | |
<excludeGroupIds> | |
<!-- 只排除业务模块相关的jar包,多个用英文逗号分割--> | |
com.bdsoft.framework.gd, | |
com.googlecode.aviator | |
</excludeGroupIds> | |
</configuration> | |
</execution> | |
</executions> | |
</plugin> |
maven的 package 打包时的详细信息 插件的执行顺序
maven-resources-plugin resources (default-resources) | |
maven-compiler-plugin compile (default-compile) | |
maven-resources-plugin testResources (default-testResources) | |
maven-compiler-plugin testCompile (default-testCompile) | |
maven-surefire-plugin test (default-test) | |
maven-jar-plugin jar (default-jar) | |
spring-boot-maven-plugin repackage (repackage) | |
spring-boot-maven-plugin repackage (default) | |
maven-dependency-plugin copy-dependencies (pra1) 【pra1 是execution 定义的id】 | |
maven-assembly-plugin single (make-assembly) |
详细日志如下:
[ | ] ------------------< com.bdsoft.framework.gd:leea-app >------------------|
[1.0.0.0 [5/6] | ] Building APP启动配置|
[ | ] --------------------------------[ jar ]---------------------------------|
.... | |
[ | ]|
[3.1.0:resources (default-resources) @ leea-app --- | ] --- maven-resources-plugin:|
['utf-8' encoding to copy filtered resources. | ] Using|
[16 resources | ] Copying|
[ | ]|
[3.8.1:compile (default-compile) @ leea-app --- | ] --- maven-compiler-plugin:|
[ | ] Changes detected - recompiling the module!|
[13 source files to D:\work\workspaces\workspace-af\xxxxxxxxxxx\gd_mbs_zfxn\xxxx\leea-app\target\classes | ] Compiling|
[ | ] /D:/work/workspaces/workspace-af/xxxxxxxxxxx/gd_mbs_zfxn/xxxx/leea-app/src/main/java/com/bdsoft/config/system/GlobalCorsConfig.java: 某些输入文件使用或覆盖了已过时的 API。|
[ | ] /D:/work/workspaces/workspace-af/xxxxxxxxxxx/gd_mbs_zfxn/xxxx/leea-app/src/main/java/com/bdsoft/config/system/GlobalCorsConfig.java: 有关详细信息, 请使用 -Xlint:deprecation 重新编译。|
[ | ]|
[3.1.0:testResources (default-testResources) @ leea-app --- | ] --- maven-resources-plugin:|
['utf-8' encoding to copy filtered resources. | ] Using|
[ | ] skip non existing resourceDirectory D:\work\workspaces\workspace-af\xxxxxxxxxxx\gd_mbs_zfxn\xxxx\leea-app\src\test\resources|
[ | ]|
[3.8.1:testCompile (default-testCompile) @ leea-app --- | ] --- maven-compiler-plugin:|
[ | ] No sources to compile|
[ | ]|
[2.22.2:test (default-test) @ leea-app --- | ] --- maven-surefire-plugin:|
[ | ] Tests are skipped.|
[ | ]|
[3.2.0:jar (default-jar) @ leea-app --- | ] --- maven-jar-plugin:|
[ | ] Building jar: D:\work\workspaces\workspace-af\xxxxxxxxxxx\gd_mbs_zfxn\xxxx\leea-app\target\gd_mbs_zfxn.jar|
[ | ]|
[2.2.7.RELEASE:repackage (repackage) @ leea-app --- | ] --- spring-boot-maven-plugin:|
[ | ] Layout: ZIP|
[with repackaged archive | ] Replacing main artifact|
[ | ]|
[2.2.7.RELEASE:repackage (default) @ leea-app --- | ] --- spring-boot-maven-plugin:|
[ | ] Layout: ZIP|
[with repackaged archive | ] Replacing main artifact|
[ | ]|
[3.1.0:copy-dependencies (pra1) @ leea-app --- | ] --- maven-dependency-plugin:|
[-9.0.37.jar to D:\work\workspaces\workspace-af\xxxxxxxxxxx\gd_mbs_zfxn\xxxx\leea-app\target\lib\tomcat-embed-websocket-9.0.37.jar | ] Copying tomcat-embed-websocket|
[-1.18.12.jar to D:\work\workspaces\workspace-af\xxxxxxxxxxx\gd_mbs_zfxn\xxxx\leea-app\target\lib\lombok-1.18.12.jar | ] Copying lombok|
.... | |
[ | ]|
[3.4.2:single (make-assembly) @ leea-app --- | ] --- maven-assembly-plugin:|
[ | ] Reading assembly descriptor: src/main/assembly/assembly.xml|
[-1.0.0.0.tar.gz | ] Building tar: D:\work\workspaces\workspace-af\xxxxxxxxxxx\gd_mbs_zfxn\xxxx\leea-app\target\gd_mbs_zfxn-dev|
[-1.0.0.0.zip | ] Building zip: D:\work\workspaces\workspace-af\xxxxxxxxxxx\gd_mbs_zfxn\xxxx\leea-app\target\gd_mbs_zfxn-dev|
[ | ]|
总结
不要盲目相信网上 各种拷贝粘贴文,有问题第一时间应该去阅读一下官方的文档,毕竟官方才是理解最深的那位,文档也是最浅显易懂的。