在阅读详细说明文档前我们先来谈谈对Maven
的一些理解,有助于从全局角度上了解Maven
的作用与意义。
Maven
是什么?通俗来讲,Maven
有助于构建工程、管理Jar
包、编译代码,还能自动运行单元测试、打包、生成报表,甚至能帮你部署项目。Maven
构建的项目均可以直接使用maven build
完成项目的编译、测试、打包,无需额外配置。Maven
是通过pom.xml
来执行任务的,其中的build
标签描述了如何编译及打包项目,而具体的编译和打包工作是通过build
标签中配置的 plugin
来完成的。当然plugin
配置不是必须的,默认情况下,Maven
会绑定以下几个插件来完成基本操作。Plugin | function | Lifecycle Phase |
---|---|---|
maven-clean-plugin | 清理上一次执行创建的目标文件 | clean |
maven-resources-plugin | 处理源资源文件、测试资源文件 | resources,testResources |
maven-compiler-plugin | 编译源文件、测试源文件 | compiler,testCompiler |
maven-surefire-plugin | 执行测试文件 | test |
maven-jar-plugin | 创建jar | package |
maven-install-plugin | 安装jar,将创建生成的jar拷贝到.m2/repository下面 | install |
maven-deploy-plugin | 发布jar | deploy |
即使在没有配置的情况下,执行mvn clean install
时,maven
会调用默认的plugin
来完成编译打包操作,具体来讲,执行mvn clean install
时会执行plugin
:
maven-clean-plugin:2.5:clean (default-clean)
maven-resources-plugin:2.6:resources (default-resources)
maven-compiler-plugin:3.1:compile (default-compiler)
maven-resources-plugin:2.6:testResources (default-testResources)
maven-compiler-plugin:3.1:testCompile (default-testCompile)
maven-surefire-plugin:2.12.4:test (default-test)
maven-jar-plugin:2.4:jar (default-jar)
maven-install-plugin:2.4:install (default-install)
plugin
进行特殊配置,需要在pom.xml
中的
标签中显示指定 plugin
和 属性配置。
org.apache.maven.plugins
maven-compiler-plugin
3.1
1.8
如上配置了maven-compiler-plugin
的版本和编译时使用的jdk
版本。
在Maven
的pom.xml
文件中,Build
相关配置包含两个部分:一个是
,另一个是
,这里我们只介绍
。
在Maven
的pom.xml
文件中,存在如下两种
标签:
说明:
被称为Project Build
,即是
的直接子元素。另一种
被称为Profile Build
,即是
的直接子元素。Profile Build
包含了基本的build
元素,而Project Build
还包含两个特殊的元素,即各种<...Directory>
和
。说明:
defaultGoal
,执行构建时默认的goal
或phase
,如jar:jar
或者package
等directory
,构建的结果所在的路径,默认为${basedir}/target
目录finalName
,构建的最终结果的名字,该名字可能在其他plugin
中被改变
标签资源往往不是代码,而是一些properties
或XML
配置文件,无需编译,构建过程中会往往会将资源文件从源路径复制到指定的目标路径。
给出各个资源在Maven
项目中的具体路径。示例如下:
说明:
resources
,build
过程中涉及的资源文件targetPath
,资源文件的目标路径filtering
,构建过程中是否对资源进行过滤,默认false
directory
,资源文件的路径,默认位于${basedir}/src/main/resources/
目录下includes
,一组文件名的匹配模式,被匹配的资源文件将被构建过程处理excludes
,一组文件名的匹配模式,被匹配的资源文件将被构建过程忽略。同时被includes
和excludes
匹配的资源文件,将被忽略。filters
,给出对资源文件进行过滤的属性文件的路径,默认位于${basedir}/src/main/filters/
目录下。属性文件中定义若干键值对。在构建过程中,对于资源文件中出现的变量(键),将使用属性文件中该键对应的值替换。testResources
,test
过程中涉及的资源文件,默认位于${basedir}/src/test/resources/
目录下。这里的资源文件不会被构建到目标构件中。
标签
给出构建过程中所用到的插件。
说明:
groupId
artifactId
version
extensions
,是否加载该插件的扩展,默认false
inherited
,该插件的configuration
中的配置是否可以被(继承该POM
的其他Maven
项目)继承,默认true
configuration
,该插件所需要的特殊配置,在父子项目之间可以覆盖或合并dependencies
,该插件所特有的依赖类库executions
,该插件的某个goal
(一个插件中可能包含多个goal
)的执行方式。一个execution
有如下设置:id
,唯一标识goals
,要执行的插件的goal
(可以有多个),如run
phase
,插件的goal
要嵌入到Maven
的phase
中执行,如verify
inherited
,该execution
是否可被子项目继承configuration
,该execution
的其他配置参数
标签在
中,
与
并列,两者之间的关系类似于
与
之间的关系。
中也配置
,其配置参数与
中的
完全一致。只是,
往往出现在父项目中,其中配置的
往往通用于子项目。子项目中只要在
中以
声明该插件,该插件的具体配置参数则继承自父项目中
对该插件的配置,从而避免在子项目中进行重复配置。
往往配置在父项目中,供所有父子项目使用。示例如下:
是执行构建过程中可能用到的其他工具,在执行构建的过程中被加入到classpath
中。
也可以通过
激活构建插件,从而改变构建的过程。
通常,通过
给出通用插件的一个具体实现,用于构建过程。
的使用示例如下:
构建Maven
项目的时候,如果没有进行特殊的配置,Maven
会按照标准的目录结构查找和处理各种类型文件。
这两个目录中的所有*.java
文件会分别在compile
和test-compile
阶段被编译,编译结果分别放到了target/classes
和targe/test-classes
目录中,但是这两个目录中的其他文件都会被忽略掉。
这两个目录中的文件也会分别被复制到target/classes
和target/test-classes
目录中。
当是web
项目时,会在target
下生成myproject
目录,myproject
是你的项目名
这个目录中的文件会被复制到target/myProject
目录中
默认会把这个目录中的所有内容复制到target/myProject/WEB-INF/classes
目录中
默认会将项目的依赖复制到target/myProject/WEB-INF/lib
This plugin provides the capability to package the artifact in an uber-jar, including its dependencies and to shade - i.e. rename - the packages of some of the dependencies.
maven-plugin-shade
插件提供了两个能力:
uber-jar
” 中shade
- 即重命名某些依赖的包由此引出了两个问题:
uber-jar
?uber-jar
也叫做 fat-jar
或者 jar-with-dependencies
,意思就是包含依赖的 jar
。shade
?shade
意为遮挡,在此处可以理解为对依赖的 jar
包的重定向(主要通过重命名的方式)。Shade
插件只有一个目标:将shade:shade
绑定到 package
阶段,用于创建 Uber jar
。问题来了,Uber jar
是什么jar
?请看如下介绍。
在 Java
中,如果要运行 Java
程序,需要一个包含 main
方法类的 jar
包或类文件,然后执行命令:
#Run a class file
java $class
#Run a java package (main class specified in manifest)
java -jar $jarfile
#Run the main method of a class, and add multiple jar packages to the classpath of the runtime
java -cp (- classpath) $path (directory/jar file /zip file) # zip file should conform to the specification of jar format
这种方法会有一些缺点,因为大多数 Java
程序都包含很多依赖项。如果要启动此程序,必须传递 classpath
以指定这些依赖的包文件,并且必须在服务器上指定类路径,这不是很方便,特别是随着 DevOps/Microservices
的普及,这种指定 classpath
的方法过于死板。我们可以直接构建聚合的 jar
包并发布或运行它。
可执行 jar
包通常意味着所有依赖的 jar
包都放在一个大的入口 jar
包中。这个入口 jar
包中包含运行时需要依赖的所有 jar
包和类文件。您可以将依赖的 jar
包直接放在入口 jar
包中,如下所示:
├─executable jar
│ ├─META-INF
│ │ ├─MANIFEST.MF
│ ├─com...
│ ├─lib
│ │ ├─io.netty....jar
│ │ ├─com.google....jar
│ │ ├─com.github....jar
│ │ ├─org.apache.....jar
您还可以将依赖的 jar
包中的文件复制到入口 jar
包中,如下所示:
├─executable jar
│ ├─META-INF
│ │ ├─MANIFEST.MF
│ ├─com...
│ ├─io.netty.... classes
│ ├─com.google.. classes
│ ├─com.github.. classes
│ ├─org.apache.. classes
Spring Boot
可以被看作是将可执行 jar
交付给数千个家庭。Spring Boot
在 spring-boot-maven-plugin
插件可以在构建过程中将所有依赖的 jar
包打包成一个入口 jar
文件,并通过 Spring Boot
的类加载器和启动类来将这些依赖 jar
包加载到可执行的入口 jar
包中,上面描述的第一种方法是:将依赖的 jar
包直接放进入口 jar
包中。
当我第一次看到这个词时,我不知所措。我不知道这个词是什么意思。优步出租车?查找信息后,我发现 Uber jar
的原始单词是 Über jar
,是一个德语单词,可以解释为 over
或 end
,但在实际上下文中,将其翻译为 everything
可能更合适。
这个术语最初是由开发人员创造的,他们认为将所有依赖项和自己的代码放入 jar
文件可以解决许多冲突。但大多数输入法很难输入 Ü
,所以被称为 Uber
。
Shade jar
就是将依赖包也打包到入口 jar
包的 jar
包,并提供对某些依赖包进行重命名的功能。例如,一个 Maven
项目依赖于许多第三方软件包,但您希望在实际打包期间重命名一些软件包。重命名的过程在这里可以称为 Shade
(着色)。
为什么要重命名依赖包呢?例如,当我们开发时还需要依赖一些第三方软件包,比如 netty
,所以我们需要在实际操作中以 –javaagent
或动态附加 jar
包的形式加载我们的代理 jar
包。这里加载的代理只能是一个独立的 jar
包,因此首先,我们需要通过在 jar
包中键入我们的代理及其依赖包来构建一个 Uber jar
。然后,我们需要考虑类包冲突的问题,因为代理中的依赖包类和目标 JVM
进程中的类可能会发生冲突,例如,代理依赖于 netty 4.1.58.final
,而目标 JVM
进程依赖于 netty 4.0.14.final
,我们的代理使用 4.0.14
中不存在的 API
。此时,程序将产生找不到方法的异常,因为目标进程已加载该类,并且不会重复加载代理程序包中具有相同全限定名的类。
在构建 Uber jar
时,可以修改并重新定位依赖包的包名,这比下载项目的源代码重构包名再打包要方便的多。比如,将 io.netty
修改为 com.github.kongwu.io.netty
,同时,Java
代码中的所有引用在重新定位后都使用被修改后的包名。这样,通过修改包名,完全避免了依赖性包类冲突的问题。Google
也开源了一个类似功能的 jar
文件,叫做 jarjar.jar
(好多 jar 啊)。
上述 relocation
行为称为 Shade
或 Shadow
。Maven
中的 Shade
插件可以将程序打包到单独的jar
包中,包括依赖项包。另一个类似的 Maven Assembly
插件也可以达到同样的效果。Gradle
中也有类似的插件,功能也很强大,也支持 Shade
功能。
maven-plugin-shade
必须和 Maven
构建生命周期中的 package
阶段绑定,也就是说,当执行 mvn package
时会自动触发 shade
。
要使用 maven-plugin-shade
,只需要在 pom.xml
的
标签下添加它的配置即可,示例如下:
...
org.apache.maven.plugins
maven-shade-plugin
3.1.0
package
shade
...
默认情况下会把项目所有的依赖都包含进最终的 jar
包中。当然,我们也可以在
标签内配置更具体的规则。
使用
排除不需要的依赖,示例如下:
classworlds:classworlds
junit:junit
jmock:*
*:xml-apis
org.apache.maven:lib:tests
log4j:log4j:jar:
使用
结合
&
标签可实现更灵活的依赖选择,示例如下:
<configuration>
<filters>
<filter>
<artifact>junit:junitartifact>
<includes>
<include>junit/framework/**include>
<include>org/junit/**include>
includes>
<excludes>
<exclude>org/junit/experimental/**exclude>
<exclude>org/junit/runners/**exclude>
excludes>
filter>
<filter>
<artifact>*:*artifact>
<excludes>
<exclude>META-INF/*.SFexclude>
<exclude>META-INF/*.DSAexclude>
<exclude>META-INF/*.RSAexclude>
excludes>
filter>
filters>
configuration>
上文中提及的
META-INF/*.SF
META-INF/*.DSA
META-INF/*.RSA
是为了解决 java.lang.SecurityException: Invalid signature file digest for Manifest main attributes
异常。
除了可以通过自定义的 filters
来过滤依赖,此插件还支持自动移除项目中没有使用到的依赖,以此来最小化 jar
包的体积,只需要添加一项配置即可。示例如下:
true
如果最终的 jar
包被其他的项目所依赖的话,直接地引用此 jar
包中的类可能会导致类加载冲突,这是因为 classpath
中可能存在重复的 class
文件。为了解决这个问题,我们可以使用 shade
提供的重定位功能,把部分类移动到一个全新的包中。示例如下:
org.codehaus.plexus.util
org.shaded.plexus.util
org.codehaus.plexus.util.xml.Xpp3Dom
org.codehaus.plexus.util.xml.pull.*
涉及标签:
:原始包名
:重命名后的包名
:原始包内不需要重定位的类,类名支持通配符例如,在上述示例中,我们把 org.codehaus.plexus.util
包内的所有子包及 class
文件(除了 ~.xml.Xpp3Dom
和 ~.xml.pull
之外的所有 class
文件)重定位到了 org.shaded.plexus.util
包内。
当然,如果包内的大部分类我们都不需要,一个个排除就显得很繁琐了。此时我们也可以使用
标签来指定我们仅需要的类,示例如下:
...
org.codehaus.plexus.util
org.shaded.plexus.util
org.codehaud.plexus.util.io.*
...
使用 maven-plugin-shade
后,最终生成的 jar
包可以包含所有项目所需要的依赖。我们会想,能不能直接运行这个 uber-jar
呢?答案是当然可以,并且十分简单,只需要指定
启动类就可以了。示例如下:
...
org.sonatype.haven.HavenCli
...
熟悉 jar
包的朋友们都知道,jar
包中默认会包含一个 MANIFEST.MF
文件,里面描述了一些 jar
包的信息。使用 java
自带的 jar
命令打包的时候可以指定 MANIFEST.MF
,其中也可以指定 Main-Class
来使得 jar
包可运行。那么使用 shade
来指定和直接在 MANIFEST.MF
文件中指定有什么区别呢?
答案是没有区别,细心的读者会发现
标签的父标签是
有一个 implementation
属性,其值为 "~.ManifestResourceTransformer"
,意思是 Manifest
资源文件转换器。上述示例指定了启动类,因此 shade
会为我们自动生成一个包含 Main-Class
的 MANIFEST.MF
文件,然后在打 jar
包时指定这个文件。
那如果我们想要完全定制 MANIFEST.MF
文件内容怎么办呢?我们可以使用
标签,示例如下:
...
org.sonatype.haven.ExodusCli
123
...