@xtccc
2017-01-04T21:02:44.000000Z
字数 5577
阅读 11154
Gradle
Gradle有一个插件 —— shadowJar,利用它可以达到以下目的:
方式1:
// build.gradle
buildscript {
repositories { jcenter() }
dependencies {
classpath 'com.github.jengelman.gradle.plugins:shadow:1.2.2'
}
}
apply plugin: 'com.github.johnrengelman.shadow'
方式2:
// build.gradle
plugins {
id 'com.github.johnrengelman.shadow' version '1.2.3'
}
如果我们想在Spark中读取Cassandra中的数据,那么需要向Spark Job提供两个依赖,即 spark-cassandra-connector 和 cassandra-driver-core 。
这样的项目,编译打包时都是可以的,但是提交到CDH Spark on Yarn集群上运行时就会出现问题:
java.lang.NoSuchMethodError: com.google.common.reflect.TypeToken.isPrimitive()Z
原因是有个Jar包(guava)发生了版本冲突。
cassandra-driver-core需要依赖guava-16.1,但是CDH自己只提供了guava-14.0.1。guava-16.1包含了方法com.google.common.reflect.TypeToken.isPrimitive,但是guava-14.0.1没有包含这个方法。(也许CDH提供了其他的版本,总之CDH提供的guava包中没有com.google.common.reflect.TypeToken.isPrimitive这个方法)。
所以,cassandra-driver-core被调用时,就在找不到上面提到的那个方法。即使我们在提交Spark Job时通过 --jars 参数提供了guava-16.1也不行,因为classpath并不会被guava-16.1改写,任然找不到这个方法。另一方面,CDH其他的组件也会用到CDH自己包含的guava版本,因此,绝对不能随意地改写CDH的classpath或者用guava-16覆盖guava-14。
有另一个可行的思路。
在为项目打包时,将guava-16.0和cassandra-driver-core也打到我们的目标Jar文件中,生成一个fat jar。同时,将fat jar中的所有com.google包名改为com.google.gridx。这样,fat jar中的cassandra-driver-core将依赖com.google.gridx.common.reflect.TypeToken.isPrimitive这个方法,且这个方法在fat jar中也存在。这样,既解决了Jar包冲突的问题,由不会影响集群中其他的jar包。
下面给出一个build文件
plugins {
id 'java'
id 'scala'
id 'com.github.johnrengelman.shadow' version '1.2.3'
}
group 'cn.gridx.scala'
version '1.0-RELEASE'
sourceCompatibility = 1.7
repositories {
mavenCentral()
maven {
url 'https://repository.cloudera.com/artifactory/cloudera-repos'
}
}
dependencies {
compile "com.datastax.spark:spark-cassandra-connector_2.10:1.5.0-M2",
"org.apache.spark:spark-core_2.10:1.5.0-cdh5.5.1"
}
shadowJar {
// 只把指定的依赖打入到fat jar中
dependencies {
// 这个依赖不一定是compile阶段的依赖
include (dependency('com.google.guava:guava:16.0.1'))
include (dependency('com.datastax.cassandra:cassandra-driver-core:2.2.0-rc3'))
}
relocate 'com.google', 'com.google.gridx'
}
运行命令
gradle clean shadowJar
然后,会生成一个名为spark-1.0-RELEASE-all.jar的fat jar,可以看到它里面原来为"com.google.*"的包名都被替换成了新的包名"com.google.gridx.*"
jar -tf build/libs/spark-1.0-RELEASE-all.jar | grep com/google/gridx
上面利用shadowJar插件来进行relocation时,构建出的fat jar会把所有的com.google
都rename成com.google.gridx
。
假设项目有2个依赖:asm:asm:3.1
和 org.ow2.asm:asm:5.1
(都是间接依赖),他们有相同的顶层包名org.objectweb.asm
,我们希望只把org.ow2.asm:asm:5.1
中的org.objectweb.asm
进行重命名,该怎么办?
有几个gradle plugins可以帮到你:
我们在写akka-cluster app时,如果想最终生成一个uber jar,那么一般用以下方法打包:
jar {
from configurations.runtime.collect {
it.isDirectory() ? it : zipTree(it)
}
}
运行时会出现异常:
Exception in thread "main" com.typesafe.config.ConfigException$Missing: No configuration setting found for key 'akka.version'
这是因为,Akka的配置方法会去读取并加载各个module(remoting, cluster)中的reference.conf文件。在我们的uber jar中,并没有把各个module中的reference.conf文件merge起来。参考 Configuration。
解决方法: 在生成uber jar时,要求gradle把所有的reference.conf文件的内容都merge起来,并将merge后的内容写入到uber jar根目录中的reference.conf文件。
在build.gradle文件中加入以下的内容:
plugins {
id 'com.github.johnrengelman.shadow' version '1.2.3'
}
shadowJar {
transform(com.github.jengelman.gradle.plugins.shadow.transformers.AppendingTransformer) {
resource = 'reference.conf'
}
}
用命令构建uber jar:
./gradlew clean shadowJar
在使用shadowJar生成了一个uber jar之后,我们往往需要发布一个包含了其他文件的ZIP包,怎样将生成的uber jar放入到发布的ZIP文件中呢?
shadowJar可以与application插件协同使用,参考 Distributing the Shadow JAR
我们可以在执行shadowJar
任务后,再执行一个任务将前面生成的uber jar放入将要发布的ZIP文件中。
plugins {
id 'com.github.johnrengelman.shadow' version '1.2.3'
}
shadowJar {
transform(com.github.jengelman.gradle.plugins.shadow.transformers.AppendingTransformer) {
resource = 'reference.conf'
}
}
task buildUberZip(type: Zip) {
from('distributions/conf/log4j2.xml') {
into 'conf'
}
from('distributions/bin/startup.sh') {
into 'bin'
}
from(configurations.runtimeOnly) {
into 'lib'
}
// 这里手动填写由shadowJar生成的uber jar
// 还有更好的方法
from('build/libs/data-service-1.0-SNAPSHOT-all.jar') {
into 'bin'
}
}
运行命令:
./gradlew clean shadowJar buildUberZip
利用task dependency简化流程
还可以让任务buildUberZip
依赖于任务shadowJar
,从而我们可以直接执行任务buildUberZip
。
plugins {
id 'com.github.johnrengelman.shadow' version '1.2.3'
}
shadowJar {
transform(com.github.jengelman.gradle.plugins.shadow.transformers.AppendingTransformer) {
resource = 'reference.conf'
}
}
task buildUberZip(type: Zip, dependsOn: shadowJar) {
from('distributions/conf/log4j2.xml') {
into 'conf'
}
from('distributions/bin/startup.sh') {
into 'bin'
}
from(configurations.runtimeOnly) {
into 'lib'
}
// 直接填入task name,代表该task生成的artifact
from(shadowJar) {
into 'bin'
}
}
现在只要运行如下命令即可:
./gradlew clean buildUberZip
我们上面在build.gradle文件中是按照这种方式来引入plugin的:
plugins {
id 'com.github.johnrengelman.shadow' version '1.2.3'
}
对于不同的Gradle版本,这种写法可能会遇到如下问题:
A problem occurred evaluating root project 'xxxx'.
No such property: env for class: org.gradle.api.internal.project.DefaultProject_Decorated
Possible solutions: ant
或者
A problem occurred evaluating project 'xxx'.
No signature of method: build_7n4pib2tgnua8g334vv0igdk8j$_run_closure1.id() is applicable for argument types: (java.lang.String) values: [com.github.johnrengelman.shadow]
Possible solutions: is(java.lang.Object), is(java.lang.Object), find(), find(), find(groovy.lang.Closure), find(groovy.lang.Closure)
如果遇到类似的问题,我们换一种方式来引入plugin:
buildscript {
repositories { jcenter() }
dependencies {
classpath 'com.github.jengelman.gradle.plugins:shadow:1.2.2'
}
}
apply plugin: 'com.github.johnrengelman.shadow'