您好!
欢迎来到京东云开发者社区
登录
首页
博文
课程
大赛
工具
用户中心
开源
首页
博文
课程
大赛
工具
开源
更多
用户中心
开发者社区
>
博文
>
项目打包的三种操作
分享
打开微信扫码分享
点击前往QQ分享
点击前往微博分享
点击复制链接
项目打包的三种操作
自猿其说Tech
2021-12-27
IP归属:未知
90000浏览
计算机编程
### 1 前言 编写项目就是拿放大镜和Bug玩捉迷藏,这些暂时不用多说。倘若有一天晚上,在攻城狮们齐心协力地完成了项目后,老大把手一挥说,“那个JK啊,去把项目打包上传到依赖仓库就回吧。” 这时候JK心里其实是想要说:“诶,我不会啊?哥别急着走诶!”奈何嘴皮子一碰,说成了“好嘞,几分钟就成!” 老大刚刚才写完代码,听到这么果断的回答,心情很是舒服,就接了一句,“打好的jar包要先在本地测好了啊,能直接运行的那种,项目应该没有Bug了,赶明儿买点儿脱骨李子吃。”听到有脱骨李吃,我瞬间就满满的干劲儿了。 话说回来,同样是使用打包,有的方式打包之后是有依赖树的,有的方式打包后很臃肿,有的方式一个jar包就能直接运行,JK之前就很是好奇啊,这次就来整理总结一下如何编译打包项目。 整篇文章可以粗分为四个部分: 1. 构建一个初始项目,用日志包输出当前日期和时间。 1. 介绍两个编译插件,简单的配置和使用。 1. 介绍打包要用到的核心标签,并且就Jar包分类和运行条件进行分析。接着就使用情况的不同分类介绍几种打包插件,提供通用的配置,进行对比。 1. 最后提及了Maven处理依赖的顺序,和自己踩过的坑,进行总结。 ### 2 构建初始项目 先把项目写完,测试完证明没有`Bug`了,咱们才能说打包的事情嘛。 首先构建一个简单的、可运行的项目来,功能就是输出当前日期时间的主函数,引用日志包输出日志。 #### 2.1 keen.date.KeenDate.java文件 ```html package xyz.clzly.keen.date; /** * Created by 魏继扩on 2021/5/19 17:55 */ public class KeenDate { private static Logger logger = LoggerFactory.getLogger(KeenDate.class); public static void keenCurrentTime(){ //定义时区,可以避免虚拟机时间与系统时间不一致的问题 TimeZone.setDefault(TimeZone.getTimeZone("Asia/Beijing")); logger.info(new Date(System.currentTimeMillis()).toString()); } public static void main(String[] args) { keenCurrentTime(); } } ``` #### 2.2 pom.xml文件所引入依赖 ```xml <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> <log4j.version>2.14.0</log4j.version> <slf4j.version>1.7.25</slf4j.version> </properties> <dependencies> <!--log4j2 开始--> <!--对外Api包,是一种日志框架,单独的话缺少实现无法应用--> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>${slf4j.version}</version> </dependency> <!--桥接包log4j-slf4j-impl起到适配的作用,版本须对应log4j-core的版本--> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-slf4j-impl</artifactId> <version>${log4j.version}</version> </dependency> <!--实现包log4j-core是对log4j2的具体实现--> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>${log4j.version}</version> </dependency> <!--log4j2 结束--> </dependencies> ``` 配置文件是`log4j2.xml`在`resources`文件夹下面,不是本文的核心重点就不附上来了。项目写好了,接下来就是要编译了。 ### 3 maven编译的两大法宝 接着说一下如何把项目编译成为二进制文件。 maven项目的编译插件,如果不特别配置的话,默认都是使用的`maven-compiler-plugin`,一般来说没什么不好的,也就是以下那么三点罢了: 1. 版本3.0之后,编译器是用的 `javax.tools.JavaCompiler`而不是使用 javac 编译器 1. 版本2.0之后,默认源(source)设置为 1.6 1. 版本2.0之后,默认目标(target)设置为 1.6。 能忍着用就用咯,没什么的,不过总有辣么几个皮的娃子(比如说我这种满脸好奇的)就想着研究研究。 #### 3.1 maven-compiler-plugin 作为纯粹的编译插件,主要作用就是指定编码,版本,强制用javac编译。通用的简单小配置: ```xml <!-- compiler插件参数设置,指定编码,版本,强制用javac编译 --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.1</version> <configuration> <source>1.8</source> <target>1.8</target> <encoding>UTF-8</encoding> <forceJavacCompilerUse>true</forceJavacCompilerUse> </configuration> </plugin> ``` 如果不想要写明该插件,也可以使用propeties标签来配置,不过需要你的maven版本支持。 ```xml <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> ``` 但是在特殊情况,例如在需要指明annotationProcessorPaths标签的时候,编译器将仅在这些类路径元素中检测注释处理器。如果省略,则默认类路径用于检测注释处理器。 ```xml <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.1</version> <configuration> 。。。 <annotationProcessorPaths> <path> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.16</version> </path> <!-- 当使用Lombok1.18.16版本+的时候,需要配置--> <path> <groupId>org.projectlombok</groupId> <artifactId>lombok-mapstruct-binding</artifactId> <version>0.2.0</version> </path> <!-- Mapstruct应该和lombok插件一致 --> <path> <groupId>org.mapstruct</groupId> <artifactId>mapstruct-processor</artifactId> <version>${org.mapstruct.version}</version> </path> </annotationProcessorPaths> </configuration> </plugin> ``` 再特别一些,还能多配置一些参数,可以在文末附上的传送门中查看更多的内容。 #### 3.2 aspectj-maven-plugin 之前曾经了解过一点点儿的AspectJ的知识,所以知道了这个编译器。他是完全支持java的,并且还有对AspectJ语言的支持,一度是我大力安利的编译插件。 用AspectJ织入项目的话,如果时机是编译期织入或者编译后织入,相较于用原生命令行,用这个插件是更为简单有效的选择;而且在其他项目中使用,效果也是很棒的。 ```xml <!--aspectj编译--> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>aspectj-maven-plugin</artifactId> <version>1.11</version> <configuration> <!--对应编译器版本--> <complianceLevel>1.8</complianceLevel> <!--对应java1.8--> <source>1.8</source> <!--对应class文件版本--> <target>1.8</target> <!--展示信息--> <showWeaveInfo>true</showWeaveInfo> <!--忽略警告--> <Xlint>ignore</Xlint> <!--设置通用编码--> <encoding>UTF-8</encoding> </configuration> <executions> <execution> <goals> <goal>compile</goal> </goals> </execution> </executions> </plugin> ``` ### 4 maven打包的三种姿势 将项目成功编译后,就是要打包了。总的来说,我了解到的有三种主流打包方式,`Jar`包,`War`包,`Pom`包,如果会用插件,还能打包出来`zip`和`tar.gz`等类型,本文主要还是讲解如何打包成为`Jar`包。 #### 4.1 常用命令意义 在真正开始打包之前,再简单看一下maven的几个基础功能: - mvn clean 将以前编译得到的旧的 class 字节码文件删除,即删除target文件夹。 - mvn compile 编译,将Java 源程序编译成 class 字节码文件。 - mvn test 测试,并生成测试报告 - mvn pakage 打包,动态 web工程打 war包,Java工程打 jar 包 - mvn install 将项目生成 jar 包打入本地仓库中,方便其他的模块调用 可以加上插件,多提供几个复杂功能: - aspectj-maven-plugin 编译插件, 实现AOP的编译期织入 - exec-maven-plugin 运行插件,实现mvn的方式运行命令行语句 在实际开发中,可以将上述命令复合起来,实现一个这样子的效果: - mvn clean install -Dmaven.test.skip=true 清理,编译,打包到本地仓库并且抛弃测试用例打包 - mvn clean compile exec:exec 清理,编译,运行 #### 4.2 Jar包分类和运行条件 ##### 4.2.1 Jar包分类 同样都是项目打包,为什么有的包就很大(超过百兆),有的包就很小(甚至不到一兆)呢?这就是FatJar(胖包)和ThinJar(瘦包)的区别了。 FatJar(胖包):通俗些讲,就是将除 java 虚拟机以外的所有依赖都打到一个Jar包里,方便直接部署和运行。当然唯一的缺点就是太胖了(体积过大)。 ThinJar(瘦包):通俗些讲,将绝大部分依赖外置到运行环境中,只保留极少部分依赖打成一个Jar包,方便传输。当然缺点就是外置依赖没有办法管理有安全风险,也需要花精力去部署运行环境。 ##### 4.2.2 运行条件 要想jar包能直接运行,需要满足两个条件: 1. 在jar包中的清单文件META-INF/MANIFEST.MF中指定程序的入口Main-Class。 要能加载到依赖包的路径。 1. 在指定了Main-Class,有了依赖包,那么就可以直接通过命令运行jar包: ``` java -jar xxx.jar ``` 如果还有错误,就是两条腿不够粗不够硬!翻译过来就是这么两个错误: 1. "no main manifest attribute, in xxx.jar"(没有设置Main-Class,或者设置的不是全限定名)。 1. ClassNotFoundException(找不到依赖包)。 #### 4.3 共享打包标签 了解了如何能够将jar包直接运行后,咱们就要想着如何打包成为满足条件的jar包了。 先解决第一个问题,如何从清单文件中寻找到启动类呢。这时候就要介绍一下Archiver标签了,作为一个处理打包的共享组件,不论使用哪种插件,都有必要更详细地了解他:Archiver。如果对这一段详细标签定义兴趣不大,只是想着打包完成的小伙伴,建议直接跳到4.4哦。 ##### 4.3.1 Archiver标签 这是由插件使用来处理打包的共享组件,不是针对任何特定插件。下表是从官网上面扒下来的,JK人力翻译,如有失误,那。。就去看原文呗,传送门在文末。 ![](//img1.jcloudcs.com/developer.jdcloud.com/0fc60bf0-bc3e-4697-a328-4fda16c6e0a820211227151403.png) ##### 4.3.2 manifest标签 ![](//img1.jcloudcs.com/developer.jdcloud.com/2209af79-2410-465f-839c-ccc6520e3a9620211227151419.png) #### 4.4 maven-jar-plugin ##### 4.4.1 代码例子 这个插件是最基础的,下载IntellijIDEA 2020.3时会默认安装`Maven 3.6.3`,而Maven自带的打包插件就是他。使用`maven-jar-plugin`插件进行打包,在运行时还需要和`maven-dependency-plugin`插件一起使用。 在`pom.xml`中配置: ```xml <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <version>2.6</version> <configuration> <archive> <manifest> <!--指定程序入口,MANIFEST.MF中的Main-Class--> <mainClass>xyz.clzly.keen.date.KeenDate</mainClass> <!--会在MANIFEST.MF加上Class-Path项并配置依赖包--> <addClasspath>true</addClasspath> <!--指定依赖包所在目录。--> <classpathPrefix>lib/</classpathPrefix> </manifest> </archive> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-dependency-plugin</artifactId> <version>2.8</version> <executions> <execution> <id>copy-dependencies</id> <phase>package</phase> <goals> <goal>copy-dependencies</goal> </goals> <!--将依赖包拷贝到指定的位置,即lib目录下。--> <configuration> <outputDirectory>${project.build.directory}/lib</outputDirectory> </configuration> </execution> </executions> </plugin> </plugins> </build> ``` 配置完成后,通过`mvn package`指令打包,会在`target`目录下生成`jar`包,并将依赖用到的`jar`包拷贝到`target/lib`目录下。 ![](//img1.jcloudcs.com/developer.jdcloud.com/b6c88124-432d-4b44-bd51-4f2dbfb17bb820211227151454.png) ##### 4.4.2 部分标签作用 实在是忍不住好奇的心,就多加了解了一点点。 - mainClass - - 默认是没有这个标签的,于是在打包生成的`META-INF/MANIFEST.MF`中就没有条目`Main-Class`了 - - 如果加上该标签,那么里面的属性值一定要是完整的限定名加类名才可以。 - addClasspath - - 这个标签默认值是`false`,于是在打包生成的`META-INF/MANIFEST.MF`中就没有条目`Class-Path`了 - - 如果改成`true`,效果就是会添加该条目,并且把内容赋为`Pom`文件中写明的依赖名称和版本,效果如下: ``` Class-Path: slf4j-api-1.7.25.jar log4j-slf4j-impl-2.14.0.jar log4j-api -2.14.0.jar log4j-core-2.14.0.jar ``` 但是此时仍然是不够的,因为如果你想要直接运行该`jar`包,还需要手动寻找所用到的依赖全都放到同级目录下才可以,大概是这个样子: ![](//img1.jcloudcs.com/developer.jdcloud.com/01a6cac2-b0df-49dc-a0ed-7c89a380505720211227151553.png) - classpathPrefix - - 主要是为了和上面的`addClasspath`标签配合的,属性值为前缀,添加了之后,生成的条目`Class-Path`就变成了这个样子: ``` Class-Path: lib/slf4j-api-1.7.25.jar lib/log4j-slf4j-impl-2.14.0.jar l ib/log4j-api-2.14.0.jar lib/log4j-core-2.14.0.jar ``` 此时的你需要创建lib文件夹,手动寻找并复制依赖后,形成的文件夹结构是: ![](//img1.jcloudcs.com/developer.jdcloud.com/25981285-b9d2-45fc-a451-16f695892a2120211227151629.png) 单独使用这一个插件的话,总是避免不了手动复制依赖的操作,所以还需要使用多一个插件`maven-dependency-plugin`来完成将依赖`jar`包复制到指定目录下的操作。 ##### 4.4.3 结构介绍 使用该插件,最终打出来的`jar`包结构如图所示: ![](//img1.jcloudcs.com/developer.jdcloud.com/4c1a4881-e719-4aaf-b3a7-3875ba45656420211227151656.png) `maven-jar-plugin`用于生成`META-INF/MANIFEST.MF`文件的部分内容: ``` Manifest-Version: 1.0 Archiver-Version: Plexus Archiver Built-By: 帅气的JK Class-Path: lib/slf4j-api-1.7.25.jar lib/log4j-slf4j-impl-2.14.0.jar l ib/log4j-api-2.14.0.jar lib/log4j-core-2.14.0.jar Created-By: Apache Maven 3.6.3 Build-Jdk: 1.8.**************** Main-Class: xyz.clzly.keen.date.KeenDate ``` 这种方式打出来的ThinJar包很清爽,所有需要的依赖都写在pom.xml之中了,从清单文件中读取依赖的内容和位置,接着去文件夹目录中寻找,如果所有依赖的jar包都存在的话就可以直接运行了! 但是接着就会面对一个问题,在移动jar包的时候一定要带上lib文件夹,否则就不能直接运行了! 有没有办法可以将内容打成一个jar包的同时,又可以运行呢?下面的插件或许就是你的选择了。 #### 4.5 maven-assembly-plugin ##### 4.5.1 代码例子 拿来即用的`pom.xml`配置: ```xml <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-assembly-plugin</artifactId> <version>3.1.0</version> <configuration> <archive> <manifest> <mainClass>xyz.clzly.keen.date.KeenDate</mainClass> </manifest> </archive> <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef> </descriptorRefs> </configuration> <!--executions标签表示在执行package打包时,执行assembly:single--> <executions> <execution> <id>make-assembly</id> <!--绑定到package生命周期阶段上--> <phase>package</phase> <goals> <goal>single</goal> </goals> </execution> </executions> </plugin> </plugins> </build> ``` 配置完成后,通过mvn package指令打包,会在target目录下生成两个jar包,以jar-with-dependencies为尾缀的即是包含了项目与依赖的jar包了。 如果不想了解该插件自带的四种配置和如何自定义配置,直接使用maven-assembly-plugin插件自带的jar-with-dependencies配置打包就可以了!不过这个插件是将所有的 jar 包里的文件都解压到一个目录里,然后再打包进一个Fatjar中。 对于复杂应用很可能会碰到同名的类相互覆盖的情况。比如多个jar包中包含相同的文件spring.handlers和spring.schemas,但是内容又不相同,在打包过程中会互相覆盖。解决方法就是将他们合并、追加成为一个文件,建议了解一下4.6的内容,提供了一种打包Spring项目的方法。 ##### 4.5.2 部分标签作用 因为有自带的四种配置,不同的配置,打包出来的结构都是不同的, 好奇的JK解压`repository/org/apache/maven/plugins/maven-assembly-plugin/3.1.0/maven-assembly-plugin-3.1.0.jar`后,看到在文件夹`maven-assembly-plugin-3.1.0/assemblies`下面存在有四个`xml`文件。 为了节省篇幅,这里只附上了四种自带配置的内容,如果对自定义配置感兴趣的,可以继续看本文的4.6.3的内容,也可以参看文末的相关链接。 - descriptorRef - - bin : 最明显的就是,会将已经打包好的jar包再打一层zip包,不仅如此,还会再打包成为`tar.gz`和`tar.bz2`类型。 ```xml <!-- START SNIPPET: bin --> <assembly xmlns="http://maven.apache.org/ASSEMBLY/2.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.0.0 http://maven.apache.org/xsd/assembly-2.0.0.xsd"> <id>bin</id> <formats> <format>tar.gz</format> <format>tar.bz2</format> <format>zip</format> </formats> <fileSets> <fileSet> <directory>${project.basedir}</directory> <outputDirectory>/</outputDirectory> <includes> <include>README*</include> <include>LICENSE*</include> <include>NOTICE*</include> </includes> </fileSet> <fileSet> <directory>${project.build.directory}</directory> <outputDirectory>/</outputDirectory> <includes> <include>*.jar</include> </includes> </fileSet> <fileSet> <directory>${project.build.directory}/site</directory> <outputDirectory>docs</outputDirectory> </fileSet> </fileSets> </assembly> <!-- END SNIPPET: bin --> ``` - jar-with-dependencies : 会将所有依赖都解压打包到生成的jar包里 ```xml <!-- START SNIPPET: jar-with-dependencies --> <assembly xmlns="http://maven.apache.org/ASSEMBLY/2.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.0.0 http://maven.apache.org/xsd/assembly-2.0.0.xsd"> <!-- TODO: a jarjar format would be better --> <id>jar-with-dependencies</id> <formats> <format>jar</format> </formats> <includeBaseDirectory>false</includeBaseDirectory> <dependencySets> <dependencySet> <outputDirectory>/</outputDirectory> <useProjectArtifact>true</useProjectArtifact> <unpack>true</unpack> <scope>runtime</scope> </dependencySet> </dependencySets> </assembly> <!-- END SNIPPET: jar-with-dependencies --> ``` 使用这个配置打包的`jar`包前三层结构如图所示: ![](//img1.jcloudcs.com/developer.jdcloud.com/3a32adae-82e4-4b8e-8057-d5f32224a89220211227152638.png) 较为常用的一个配置了,不过我还不是很满意,觉得打包依赖就算了,不应该解压出来的,也就是在这里衍生了自定义配置的想法。 - - src :只将源码目录下的文件打包,里面就是`*.java`和`*.xml`的文件。 ```xml <!-- START SNIPPET: src --> <assembly xmlns="http://maven.apache.org/ASSEMBLY/2.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.0.0 http://maven.apache.org/xsd/assembly-2.0.0.xsd"> <id>src</id> <formats> <format>tar.gz</format> <format>tar.bz2</format> <format>zip</format> </formats> <fileSets> <fileSet> <directory>${project.basedir}</directory> <includes> <include>README*</include> <include>LICENSE*</include> <include>NOTICE*</include> <include>pom.xml</include> </includes> <useDefaultExcludes>true</useDefaultExcludes> </fileSet> <fileSet> <directory>${project.basedir}/src</directory> <useDefaultExcludes>true</useDefaultExcludes> </fileSet> </fileSets> </assembly> <!-- END SNIPPET: src --> ``` - descriptors - - descriptor:指定自定义配置文件所在的路径和文件名。后来实在没忍住好奇心,就多写了一小节。 ##### 4.5.3 自定义配置 本身自带的四种标签就可以满足基本使用了,但是该插件最厉害的在于可以定制自己的打包方式和内容。实在是很好奇啊,如何才能自定义打包方式和内容呢?我的初衷便是,所有的jar包是不用解压的,只需要直接放进去就好了。 先看结果,在打包后会出现一个以keen-jar-with-dependencies尾缀的jar文件,解压后可以看到如图所示的文件结构: ![](//img1.jcloudcs.com/developer.jdcloud.com/f5072c78-d46d-41d0-bd58-5b2b4563f88720211227152736.png) 需要多说一点,这样子是没有办法找到启动类的,因为按照MANIFEST.MF清单中的启动类去找class文件会找不到的。 在上面简单看了一下四个默认配置的内容,准备根据jar-with-dependencies进行一定的修改。新建配置文件后,修改对应的id和unpack标签。 resources/assembly/keen-jar-with-dependencies.xml文件内容如下: ```xml <assembly xmlns="http://maven.apache.org/ASSEMBLY/2.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.0.0 http://maven.apache.org/xsd/assembly-2.0.0.xsd"> <id>keen-jar-with-dependencies</id> <formats> <format>jar</format> </formats> <includeBaseDirectory>false</includeBaseDirectory> <dependencySets> <dependencySet> <outputDirectory>/</outputDirectory> <useProjectArtifact>true</useProjectArtifact> <unpack>false</unpack> <scope>runtime</scope> </dependencySet> </dependencySets> </assembly> ``` 然后在项目的`pom.xml`配置一下插件,多加了一个`descriptor`标签。 ```xml <configuration> <archive> <manifest> <mainClass>xyz.clzly.keen.date.KeenDate</mainClass> </manifest> </archive> <descriptors> <descriptor>src/main/resources/assembly/keen-jar-with-dependencies.xml</descriptor> </descriptors> </configuration> ``` 因为在打包的时候是一定会将原项目打包成单独的jar包,再进一步组合,最后生成的jar包会以配置的ID标签为尾缀。所以可以同时存在多个不同ID的配置,而且也不会和默认配置冲突,是允许多个配置一起起作用。 在项目较为复杂的情况下,如果碰到同名的类相互覆盖的情况,可能会有打包失败的风险,那样子的话,可以使用下面的插件来实现。 #### 4.6 maven-shade-plugin ##### 4.6.1 代码例子 为了避免碰到同名的类相互覆盖的情况,可以使用AppendingTransformer标签来对文件内容追加合并。可以一定程度上避免同名的类相互覆盖的情况。最终FatJar也不会带入传递依赖冲突问题给下游。 使用maven-shade-plugin插件打包,在pom.xml中配置: ```xml <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>3.2.1</version> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration> <!--保留原项目的jar包和pom.xml--> <shadedArtifactAttached>true</shadedArtifactAttached> <!--指定尾缀--> <shadedClassifierName>keen-jar-with-dependencies</shadedClassifierName> <!--在打包过程中对文件进行处理--> <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> <!--指定程序入口,MANIFEST.MF中的Main-Class--> <mainClass>xyz.clzly.keen.date.KeenDate</mainClass> </transformer> <!--如果多个jar包在META-INF文件夹下含有相同的文件,那么需要将他们合并到一个文件里--> <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer"> <resource>META-INF/spring.handlers</resource> </transformer> <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer"> <resource>META-INF/spring.schemas</resource> </transformer> <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer"> <resource>META-INF/spring.factories</resource> </transformer> <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer"> <resource>META-INF/spring.tld</resource> </transformer> <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer"> <resource>META-INF/spring-form.tld</resource> </transformer> <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer"> <resource>META-INF/spring.tooling</resource> </transformer> </transformers> </configuration> </execution> </executions> </plugin> </plugins> </build> ``` 配置完成后,执行mvn package即可打包成为包含了所有依赖的单文件。 ##### 4.6.2 部分标签作用 - shadedArtifactAttached 保留原项目的jar包和pom.xml - shadedClassifierName 指定打包后的尾缀 - transformers - - ManifestResourceTransformer 指定清单文件内容 - - AppendingTransformer 指定追加文件 再特别一些,还能多配置一些参数,例如可以修改别人jar包的package重新定位类位置,或者聚合不同依赖在META-INF文件夹下相同名称的xml文件等等,都可以在文末附上的传送门中查看更多的内容。 ##### 4.6.3 结构介绍 在target目录下会生成两个jar包,一个是原项目的打包文件,另一个是添加了多个依赖后的打包文件,后者的FatJar包前三层结构如图所示: ![](//img1.jcloudcs.com/developer.jdcloud.com/d03fb3eb-90e7-4eb7-9cbc-c886885608d920211227152946.png) #### 4.7 三种打包姿势对比 就以上文中所配置的结果进行评测,在不算自定义`assembly`配置的前提下,结果如下表: ![](//img1.jcloudcs.com/developer.jdcloud.com/1c6a09c4-5191-42e3-844f-1ff869b0d17220211227153003.png) ### 5 Maven杂谈 #### 5.1 打包到本地仓库 打包后,会在target文件夹里生成jar包,如果使用install命令,可以将jar包和pom.xml文件复制到本地仓库,方便其他项目引用。如果你打包成功了的话,那么就可以在仓库的包名-项目名-版本号路径下面,至少看到一个pom文件(用来获取依赖树的)和一个jar包(存储依赖的内容)。 如果是一个大项目,里面很多小模块(module)的结构,那么倘若你单独install一个模块,因为此时母项目的pom文件不能在本地仓库中读取到,会提示缺少凭证。在另外一个项目中进行引用的话,因为缺少间接引用的依赖会看不到依赖树,而且也缺少运行所需要的类。 假设这种情况,我只修改了一个小模块,那么每次都要将整个母项目都一块打包吗?肯定不是的,只需要在母项目的路径下运行命令即可。 ``` mvn install -pl maven-shade -am -e -B ``` - -pl:构建制定的模块,后接模块列表,之间用逗号分隔 - -am:同时构建所列模块的依赖模块 - -B:使用批处理模式构建项目,能够避免等待人工交互而造成的挂起状态。 - -e:如果构建出现异常,会打印完整的异常堆栈 #### 5.2 传递性依赖优先级 如果一个项目中,有直接引用和间接引用同一个包的不同版本,那么是哪个生效呢? 首先遵循短路优先的原则。如直接写明在本项目pom.xml文件中的,优先级高于间接引用的。 其次遵循声明顺序的原则。在pom.xml文件中同时写明了两个版本的依赖,那么以最先声明的优先级更高。 如果将一个项目的所有依赖(包括间接引用)抽象成为一个图,以广度优先遍历该依赖图的顺序为准。 #### 5.3 其他项目引用依赖 有的时候,从本地仓库导入依赖,明明pom文件(用来获取依赖树的)和jar包(存储依赖的内容)都存在,但是引用的时候就是没有依赖树。 真正的问题就在于pom.xml文件的格式。 一般来说,本地项目进行一串compile,package和install操作,是不会出现问题的,因为在这个过程中会校验,父包存在且写明了本模块为module。 ```xml <parent> <artifactId>keen-package</artifactId> <groupId>xyz.clzly</groupId> <version>1.0.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>keen-package-jar</artifactId> ``` 此时如果`pom.xml`文件中存在可传递依赖,在其他项目引用依赖的时候应该就可以看到依赖树的。 不过如果在手动上传私库中出现了差错,或者配置`deploy`插件出了问题,那么引下来的`pom.xml`可能就是自动生成的非法格式: ```xml <parent> <artifactId>keen-package-jar</artifactId> <groupId>xyz.clzly</groupId> <version>1.0.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>keen-package-jar</artifactId> ``` 找不到父级依赖的话,会将这个样子的包解析成为单个的`jar`包,不会再继续解析内部的依赖,所以在其他项目引用依赖的时候就看不到依赖树了。 ### 6 小结和感谢 小伙伴们知道了maven编译是有两种插件可供选择的,知道了三种打包的插件和应用的小例子,还能够对使用不同配置打包出来的jar包区分结构差异,能够在不同的情况下选择最合适的插件去打包,最终得到可以直接运行的jar包。 读到这里,JK我就算是整理完了Maven打包的内容。打卡下班,明天就吃好吃的脱骨李了。感谢现在这个好奇的我,下面是一些传送门,方便大家更深入地了解。 如果想要复现一下相关的内容,可以参看我的测试项目,希望可以帮到伙伴咧。 ------------ ###### 自猿其说Tech-京东物流技术发展部 ###### 魏作者:魏继扩
原创文章,需联系作者,授权转载
上一篇:iOS synchronized底层原理分析
下一篇:以结果为导向 勇于担当铸就反爬防刷神盾的女汉子——访京东零售技术与数据中心潘姣
相关文章
Taro小程序跨端开发入门实战
Flutter For Web实践
配运基础数据缓存瘦身实践
自猿其说Tech
文章数
426
阅读量
2149964
作者其他文章
01
深入JDK中的Optional
本文将从Optional所解决的问题开始,逐层解剖,由浅入深,文中会出现Optioanl方法之间的对比,实践,误用情况分析,优缺点等。与大家一起,对这项Java8中的新特性,进行理解和深入。
01
Taro小程序跨端开发入门实战
为了让小程序开发更简单,更高效,我们采用 Taro 作为首选框架,我们将使用 Taro 的实践经验整理了出来,主要内容围绕着什么是 Taro,为什么用 Taro,以及 Taro 如何使用(正确使用的姿势),还有 Taro 背后的一些设计思想来进行展开,让大家能够对 Taro 有个完整的认识。
01
Flutter For Web实践
Flutter For Web 已经发布一年多时间,它的发布意味着我们可以真正地使用一套代码、一套资源部署整个大前端系统(包括:iOS、Android、Web)。渠道研发组经过一段时间的探索,使用Flutter For Web技术开发了移动端可视化编程平台—Flutter乐高,在这里希望和大家分享下使用Flutter For Web实践过程和踩坑实践
01
配运基础数据缓存瘦身实践
在基础数据的常规能力当中,数据的存取是最基础也是最重要的能力,为了整体提高数据的读取能力,缓存技术在基础数据的场景中得到了广泛的使用,下面会重点展示一下配运组近期针对数据缓存做的瘦身实践。
自猿其说Tech
文章数
426
阅读量
2149964
作者其他文章
01
深入JDK中的Optional
01
Taro小程序跨端开发入门实战
01
Flutter For Web实践
01
配运基础数据缓存瘦身实践
添加企业微信
获取1V1专业服务
扫码关注
京东云开发者公众号