Maven 高级玩法
实用技巧
Maven 提速
多线程
1 | # 用 4 个线程构建,以及根据 CPU 核数每个核分配 1 个线程进行构建 |
跳过测试
1 | -DskipTests # 不执行测试用例,但编译测试用例类生成相应的 class 文件至 target/test-classes 下 |
编译失败后,接着编译
1 | # 如果是 Apache Eagle 之类带有几十个子项目的工程,如果从头编译所有的模块,会很耗功夫 |
跳过失败的模块,编译到最后再报错
1 | # 还可以简写为 -fae |
不等到编译结束,发现错误立即报错退出
1 | # 还可以简写为 -ff |
编译结果永远显示为成功的
1 | # 还可以简写为 -fn |
使用 Aliyun 国内镜像
1 | <mirror> |
指定 Repository 目录
1 | <!-- Default: ~/.m2/repository --> |
如何使用 Maven 编译指定 module
1 | $ mvn install -pl <module_name> -am |
如何使用 Maven 编译跳过 module
1 | $ mvn install -pl <!module_name> |
Maven 标准目录结构
1 | src/main/java Application/Library sources |
如何在 Maven 中使用多个 source
1 | <plugin> |
Using Scala UnitTest by Maven
安装 Scala
Maven 依赖
1 | <dependency> |
创建 unittest 需要的 trait
1 | import org.scalatest._ |
编写测试
1 | import spark.streaming.detect.SendNetflow |
Tips: Full code is here.
Using slf4j by Maven
Maven 配置
1 | <slf4j.version>1.7.12</slf4j.version> |
logback.xml in resources directory
1 |
|
编码
示例
1 | private static final Logger _log = LoggerFactory.getLogger(ZKEventWatch.class); |
遇到的坑
SLF4J multi bindings
- 描述
1 | SLF4J: Class path contains multiple SLF4J bindings. |
- 解决
1 | <!-- 使用 Apache Curator 需要 exclusion slf4j-log4j12,否则会出现问题 --> |
Tips: Full code is here.
导出依赖 Jar
如何利用 Maven 将依赖的 jar 都导入到一个文件夹下
1 | <build> |
1 | $ mvn clean dependency:copy-dependencies |
Profiles
根据操作系统自动选择 Profile
1 | <profiles> |
指定 Profile
1 | # 激活指定的 profile |
Tips: Full code is here.
日志级别
-Dorg.slf4j.simpleLogger.defaultLogLevel=error
Generate Code for Antlr
1 | $ mvn clean install -T 1C -DskipTests=true -Dorg.slf4j.simpleLogger.defaultLogLevel=error -B |
Maven Properties
1 | $ mvn clean install -Dmy.property=propertyValue |
Maven Check Style
1 | $ mvn clean install -Dcheckstyle.skip |
Maven Update
1 | $ mvn clean install -U |
Maven Dependency
Pre-download
1 | $ mvn dependency:go-offline |
Version-compare
1 | # 在开发一些 Coprocessor 的时候,需要保证和 HBase 集群的依赖 jar 版本一致,可以使用该方法 |
Maven Proxy
命令行设置
1 | # Linux (bash) |
配置文件
1 | $ vim $MAVEN_HOME/conf/settings.xml |
Avro 插件
创建 avsc 文件
1 | { |
使用 avro-tools 工具生成 Avro 类
1 | # 下载 avro-tools |
使用 avro-plugin 插件生成 Avro 类
1 | <properties> |
Tips: Full code is here.
Shade 插件解决 Jar 多版本共存
适用场景
在整合大型项目时,可能都依赖 commons-collections 此类的工具包,但是也可能 commons-collections 的版本却因为不一致,导致冲突。可以通过 Shade 插件的 relocation
方法进行重定向,此时冲突的依赖 package 名,会被重命名,并且依赖该 jar 的程序,都会被自动替换为新的 package 名
基本用法
指定 Shade 插件的版本
1 | <properties> |
实例一
除了 org.mapdb.* 路径之外的 org.apache.commons.collections
包路径,将被重命名为 com.yuzhouwan.org.apache.commons.collections
1 | <plugin> |
实例二
除了 org.glassfish.* 路径之外的 javax.ws
包路径,将被重命名为 shade.javax.ws
。同时,org.apache.curator
包路径,将被重命名为 shade.org.apache.curator
。另外,将 shadedArtifactAttached
参数指定为 true 之后,可以避免子模块中出现找不到 shade 后的新包路径的问题
1 | <plugin> |
踩过的坑
Error creating shaded jar: Error in ASM processing class
描述
1 | Failed to execute goal org.apache.maven.plugins:maven-shade-plugin:2.1:shade (default) on project base-k2d-flow: Error creating shaded jar: Error in ASM processing class com/yuzhouwan/bigdata/k2d/K2DPartitioner.class: 52264 -> [Help 1] |
解决
升级 maven-shade-plugin
版本到 2.4.x
通过 Shade 将问题解决后,部署上线后,依赖冲突复现
解决
因为官方明确指出 java.io.File.listFiles()
方法返回的文件序列顺序,是不做保证的。所以,上线后运行环境发生变化,加载 lib
目录下的 jar 包
顺序,可能会发生变化。这时候,就需要利用不同 lib
目录,并修改 classpath 的方式,将 jar 包
加载顺序确定下来
例如,在整合 Druid 这类依赖树比较庞大的工程,就遇到了这么一种情况。Druid 中 com.sun.jersey (1.19) 和 Dataflow 中 org.glassfish.jersey (2.25.1) 发生冲突。增加了上述实例二中的 Shade 操作仍然会在线上环境,复现依赖冲突。这时,我们可以通过增加一个 lib_jersey
目录,存放 javax.ws.rs-api-2.1.jar
,并修改 classpath 为 lib_jersey/*:lib/*
。以此,保证了 lib_jersey
中的依赖得以优先加载,从而解决冲突
报错 Invalid signature file digest for Manifest main attributes
描述
1 | java.lang.SecurityException: Invalid signature file digest for Manifest main attributes |
解决
1 | <build> |
1 | # 之后,也可以通过 zip 命令,将 META-INF 目录下的相关文件,从 jar 包中删除 |
Assembly 插件
重命名 jar 包
1 | <files> |
不同模块的依赖,打包到不同的目录下
具体步骤
第一步,先在负责打包分发的 distribution 模块中,设置 pom.xml 文件
1 | <properties> |
第二步,创建好对应的 bin.xml 和 src.xml
1 | <assembly xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" |
1 | <assembly xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" |
构建完成之后,即可看到 yuzhouwan-ai
和 yuzhouwan-bigdata
模块的依赖,分别被打包到了 plugins/ai
和 plugins/bigdata
目录下
1 | # 需要预先安装 tree 命令(yum install tree -y) |
踩过的坑
模块间存在版本冲突的 jar 包
问题描述
1
Currently, inclusion of module dependencies may produce unpredictable results if a version conflict occurs
解决
如果上述
yuzhouwan-ai
和yuzhouwan-bigdata
模块,与yuzhouwan-common
模块存在版本冲突的 jar 包,则需要将bin.xml
拆解成bin-common.xml
、bin-ai.xml
和bin-bigdata.xml
三个 assembly 配置文件,依次进行构建(此时需要将 id 设置成一样的,不然会被构建到不同的目录下)。否则,版本冲突的 jar 包,将只能保留其中一个版本,且具体保留哪个版本是不确定的
常见问题
如何使用其他语言编译出来的 Jar
Clojar
如果需要在 Maven 中,添加新的 Repository,这里以 Clojar 为例,配置如下:
1 | <repositories> |
本地缓存
Maven 中出现 Repository 被缓存在本地,则可以进一步添加下面属性,以配置更新策略:
1 | <repository> |
下载问题
已 download,未 import
如果发现 pom.xml
中的 依赖虽然在 .m2
中被下载了,但是没有被 import
到项目中
尝试在 Intellij Idea 的 setting 中 找到 Maven -> Ignored Files
,看看对应的 pom.xml
有没有被勾选
编译问题
JDK 版本不一致
描述
1 | Error:java: Compilation failed: internal java compiler error |
解决
调整 compiler 的级别,并在 pom.xml
中添加 build 标签,规定 compiler 的级别
1 | <build> |
Scala 版本不一致
描述
1 | [ERROR] error: error while loading <root>, Error accessing D:\.m2\repository\org\apache\curator\curator-client\2.4.0\curator-client-2.4.0.jar |
解决
查看 pom 文件中指定的 Scala 版本,切换到对应的版本,进行编译即可
存在没有下载好的依赖
描述
1 | Error creating properties files for forking; nested exception is java.io.IOException: No such file or directory |
解决
安装依赖
1 | $ mvn dependency::tree |
离线 build (offline)
1 | $ mvn install -o |
本地 mvn clean install 到 .m2/repository 中的 jar 和 仓库中的不一致时
描述
1 | "Class not found 'xxxx' Empty test suite" in java unittest, after change the modules' names |
解决
1 | Maven -> Reimport -> Generte Source and Update Folder |
打包存在脏程序
war:war
之前需要先执行 mvn clean install
。否则,会因为 profiles
的缘故,导致 war
包中 缺少文件
语法问题
Maven 中无法识别 ${project.version}
需要将所有(除了 root / parent 模块)的 <version>1.0</version>
中的具体版本号(如 1.0.0
)全部替换成 "${project.version}"
插件问题
xxx module must not contain source root. the root already belongs to module xxx
解决
1 | Artifacts Setting -> Modules -> Sourc tab |
ExecutionException The forked VM terminated without properly saying goodbye
描述
1 | [ERROR] Failed to execute goal org.apache.maven.plugins:maven-surefire-plugin:2.19.1:test (default-test) on project eagle-app-streamproxy: ExecutionException The forked VM terminated without properly saying goodbye. VM crash or System.exit called? |
解决
1 | <plugin> |
详见:Eagle-RP-897
Fatal error compiling: 无效的目标发行版: 1.8
需要在 IDE 的 Project Structure(Ctrl+Alt+Shift+S)
里设置 Project JDK
为 jdk1.8
,并在 Settings(Ctrl+Alt+S)
里设置 Java Compiler
的 bytecode version
为 1.8
Assembly 插件报错 java.lang.StackOverflowError
解决
1 | # 调大默认的堆栈大小 |
补充
1 | -Xms:jvm 进程启动时分配的内存 |
protobuf-maven-plugin 找不到 protoc 运行程序
解决
本地安装 Protobuf 即可
1 | $ gcc --version |
如何跳过 gpg 插件
增加 <skip>true</skip>
配置即可
1 | <plugin> |
Permission denied of shell script
解决
1 | # 修改 exec-maven-plugin 中需要用到脚本即可 |
依赖问题
Maven 中央仓库找不到对应版本的依赖
如何将其他项目的 jar 安装到 maven 仓库中
利用命令安装
1 | $ mvn install:install-file -Dfile=<jar 包的位置> -DgroupId=<上面的 groupId> -DartifactId=<上面的 artifactId> -Dversion=<上面的 version> -Dpackaging=jar -DgeneratePom=true -DpomFile=<指定一个 pom.xml 添加进去> |
利用插件安装
1 | <!-- 注意:${project.basedir} 是当前模块的根目录,需要把依赖包,放对位置 --> |
控制 Scope 范围
1 | # compile(编译范围) |
引入某一个本身依赖树很庞大的 Dependency
1 | # 依赖列表 |
Detected both log4j-over-slf4j.jar AND slf4j-log4j12.jar on the class path, preempting StackOverflowError.
解决
1 | <spark.scala.version>2.11</spark.scala.version> |
Could not find artifact jdk.tools:jdk.tools:jar:1.7 at specified path /Library/Java/JavaVirtualMachines/jdk-12.0.2.jdk/Contents/Home/../lib/tools.jar
描述
Hive 中存在 jdk.tools 依赖,需要特定的 JDK 版本
1 | <dependencies> |
解决
通过 exclusion 标签将 jdk.tools 去除掉即可。如果仍然需要 jdk.tools 依赖,则可以另外单独增加 jdk.tools 的 dependency,并指定好正确的 JDK 版本
1 | <dependency> |
zip file is empty
描述
编译项目失败,报错:
1 | $ cd yuzhouwan |
1 | [INFO] ------------------------------------------------------------------------ |
解决
删掉报错的 jar 包文件,即可
1 | $ rm -f /Users/yuzhouwan/.m2/repository/org/apache/calcite/calcite-linq4j/1.33.0/calcite-linq4j-1.33.0.jar |
编码问题
Malformed \uxxxx encoding
描述
1 | $ cd yuzhouwan |
1 | # ... |
解决
1 | grep -rnw ~/.m2 -e '\u0000' |
1 | Binary file /Users/yuzhouwan/.m2/repository/net/minidev/json-smart/resolver-status.properties matches |
1 | # 将 .m2 目录下包含 \u0000 的文件移除后,便可以编译通过 |