[关闭]
@xtccc 2017-01-04T13:02:44.000000Z 字数 5577 阅读 10794

shadow/shade

给我写信
GitHub

此处输入图片的描述

Gradle




1. 介绍


1.1 shadowJar的用途

Gradle有一个插件 —— shadowJar,利用它可以达到以下目的:

  1. 为我们的项目生成一个fat jar,即将所有的依赖(或者部分指定的依赖)打入到最终生成的目标JAR文件中
  2. 将目标JAR文件中的某个package name,用另一个package name来替换
  3. 将各个资源文件的内容merge到一个文件中


1.2 引入shadowJar的方式

方式1:

  1. // build.gradle
  2. buildscript {
  3. repositories { jcenter() }
  4. dependencies {
  5. classpath 'com.github.jengelman.gradle.plugins:shadow:1.2.2'
  6. }
  7. }
  8. apply plugin: 'com.github.johnrengelman.shadow'



方式2:

  1. // build.gradle
  2. plugins {
  3. id 'com.github.johnrengelman.shadow' version '1.2.3'
  4. }



2. 重命名package name


2.1 use case

如果我们想在Spark中读取Cassandra中的数据,那么需要向Spark Job提供两个依赖,即 spark-cassandra-connectorcassandra-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.0cassandra-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包。



2.2 example

下面给出一个build文件

  1. plugins {
  2. id 'java'
  3. id 'scala'
  4. id 'com.github.johnrengelman.shadow' version '1.2.3'
  5. }
  6. group 'cn.gridx.scala'
  7. version '1.0-RELEASE'
  8. sourceCompatibility = 1.7
  9. repositories {
  10. mavenCentral()
  11. maven {
  12. url 'https://repository.cloudera.com/artifactory/cloudera-repos'
  13. }
  14. }
  15. dependencies {
  16. compile "com.datastax.spark:spark-cassandra-connector_2.10:1.5.0-M2",
  17. "org.apache.spark:spark-core_2.10:1.5.0-cdh5.5.1"
  18. }
  19. shadowJar {
  20. // 只把指定的依赖打入到fat jar中
  21. dependencies {
  22. // 这个依赖不一定是compile阶段的依赖
  23. include (dependency('com.google.guava:guava:16.0.1'))
  24. include (dependency('com.datastax.cassandra:cassandra-driver-core:2.2.0-rc3'))
  25. }
  26. relocate 'com.google', 'com.google.gridx'
  27. }



运行命令

  1. gradle clean shadowJar



然后,会生成一个名为spark-1.0-RELEASE-all.jar的fat jar,可以看到它里面原来为"com.google.*"的包名都被替换成了新的包名"com.google.gridx.*"

  1. jar -tf build/libs/spark-1.0-RELEASE-all.jar | grep com/google/gridx


2.3 只重命名指定dependency中的package

上面利用shadowJar插件来进行relocation时,构建出的fat jar会把所有的com.google都rename成com.google.gridx

假设项目有2个依赖:asm:asm:3.1org.ow2.asm:asm:5.1(都是间接依赖),他们有相同的顶层包名org.objectweb.asm,我们希望只把org.ow2.asm:asm:5.1中的org.objectweb.asm进行重命名,该怎么办?

有几个gradle plugins可以帮到你:




3. 整合资源文件


3.1 use case

我们在写akka-cluster app时,如果想最终生成一个uber jar,那么一般用以下方法打包:

  1. jar {
  2. from configurations.runtime.collect {
  3. it.isDirectory() ? it : zipTree(it)
  4. }
  5. }

运行时会出现异常:

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文件。



3.2 example

在build.gradle文件中加入以下的内容:

  1. plugins {
  2. id 'com.github.johnrengelman.shadow' version '1.2.3'
  3. }
  4. shadowJar {
  5. transform(com.github.jengelman.gradle.plugins.shadow.transformers.AppendingTransformer) {
  6. resource = 'reference.conf'
  7. }
  8. }

用命令构建uber jar:

  1. ./gradlew clean shadowJar




4. 与distribution/application插件协同使用


在使用shadowJar生成了一个uber jar之后,我们往往需要发布一个包含了其他文件的ZIP包,怎样将生成的uber jar放入到发布的ZIP文件中呢?

4.1 与application插件协同使用

shadowJar可以与application插件协同使用,参考 Distributing the Shadow JAR


4.2 直接将uber jar放入发布的ZIP文件中

我们可以在执行shadowJar任务后,再执行一个任务将前面生成的uber jar放入将要发布的ZIP文件中。

  1. plugins {
  2. id 'com.github.johnrengelman.shadow' version '1.2.3'
  3. }
  4. shadowJar {
  5. transform(com.github.jengelman.gradle.plugins.shadow.transformers.AppendingTransformer) {
  6. resource = 'reference.conf'
  7. }
  8. }
  9. task buildUberZip(type: Zip) {
  10. from('distributions/conf/log4j2.xml') {
  11. into 'conf'
  12. }
  13. from('distributions/bin/startup.sh') {
  14. into 'bin'
  15. }
  16. from(configurations.runtimeOnly) {
  17. into 'lib'
  18. }
  19. // 这里手动填写由shadowJar生成的uber jar
  20. // 还有更好的方法
  21. from('build/libs/data-service-1.0-SNAPSHOT-all.jar') {
  22. into 'bin'
  23. }
  24. }



运行命令:

./gradlew clean shadowJar buildUberZip



利用task dependency简化流程

还可以让任务buildUberZip依赖于任务shadowJar,从而我们可以直接执行任务buildUberZip

  1. plugins {
  2. id 'com.github.johnrengelman.shadow' version '1.2.3'
  3. }
  4. shadowJar {
  5. transform(com.github.jengelman.gradle.plugins.shadow.transformers.AppendingTransformer) {
  6. resource = 'reference.conf'
  7. }
  8. }
  9. task buildUberZip(type: Zip, dependsOn: shadowJar) {
  10. from('distributions/conf/log4j2.xml') {
  11. into 'conf'
  12. }
  13. from('distributions/bin/startup.sh') {
  14. into 'bin'
  15. }
  16. from(configurations.runtimeOnly) {
  17. into 'lib'
  18. }
  19. // 直接填入task name,代表该task生成的artifact
  20. from(shadowJar) {
  21. into 'bin'
  22. }
  23. }



现在只要运行如下命令即可:

./gradlew clean buildUberZip




5. 错误与问题


我们上面在build.gradle文件中是按照这种方式来引入plugin的:

  1. plugins {
  2. id 'com.github.johnrengelman.shadow' version '1.2.3'
  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:

  1. buildscript {
  2. repositories { jcenter() }
  3. dependencies {
  4. classpath 'com.github.jengelman.gradle.plugins:shadow:1.2.2'
  5. }
  6. }
  7. apply plugin: 'com.github.johnrengelman.shadow'
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注