[实践]使用JarJar优雅的发布依赖包


作为服务需求方每次在项目中添加一个依赖都提心吊胆,怕新依赖会引入Jar包冲突、ClassNotFound问题。甚至有时新依赖使用的第三方包与项目中已有的版本完全不兼容,这就迫使需求方或冲突方不得不重新修改项目。因此如何为使用者发布一个第三方依赖尽可能少的包,体现了一个服务提供者的友好态度。

使用JarJar重封装减少发布包的依赖

Spring框架体积庞大、功能繁杂但是它的第三方依赖仅仅只有Commons Log这是如何做到的呢?

其实在Spring的实现中也大量了使用cglibasm等工具包,但是 Spring 并没有直接引入依赖,而是采用将某个版本的 Jar 重新打包到自己的 package 之下的方式引入依赖。这相当于将工具包的代码拷贝到自己的项目中,使工具包里面所有类的包名都在自己的命名空间之下,从而避免了自己和其它依赖共同工具包项目之间的冲突。如果真的通过拷贝源文件实现重新发包,恐怕这个修改会非常繁琐而且容易出错。

通过 Spring 的 API 文档可以清楚的看到这一点: img

Spring使用了 Jar Jar Links 实现这个功能。

build.gradle

task cglibRepackJar(type: Jar) { repackJar ->
  repackJar.baseName = "spring-cglib-repack"
  repackJar.version = cglibVersion

  doLast() {
    project.ant {
      taskdef name: "jarjar", classname: "com.tonicsystems.jarjar.JarJarTask",
        classpath: configurations.jarjar.asPath
      jarjar(destfile: repackJar.archivePath) {
        configurations.cglib.each { originalJar ->
          zipfileset(src: originalJar)
        }
        // repackage net.sf.cglib => org.springframework.cglib
        rule(pattern: "net.sf.cglib.**", result: "org.springframework.cglib.@1")
        // as mentioned above, transform cglib"s internal asm dependencies from
        // org.objectweb.asm => org.springframework.asm. Doing this counts on the
        // the fact that Spring and cglib depend on the same version of asm!
        rule(pattern: "org.objectweb.asm.**", result: "org.springframework.asm.@1")
      }
    }
  }
}

JarJar介绍

JarJarLinks可以很方便的重新打包并封装到自己的发布中,这样做有两大好处:

  • 便捷的创建一个无依赖的单一文件的发布
  • 避免自身对特定版本包的依赖造成的与其它程序冲突

JarJar包含一个继承于内建jar任务的Ant Task完成代码正常的打包工作,通过zipfileset元素指定内嵌的jar 文件,另外添加了一个新的规则配置用来描述内嵌 jar 文件的重命名规则。JarJar使用ASM进行bytecode转换方式来实现变更reference操作,并且提供一个特殊的handling来迁移资源文件和进行字符串字面量的转换工作。

JarJar应用示例

基于Ant使用JarJar

一般情况下我们在Ant会引入如下的task

<target name="jar" depends="compile">
    <jar jarfile="dist/example.jar">
        <fileset dir="build/main"/>
    </jar>
</target>

使用JarJarLinks我们可以使用以下配置代替上面功能,因为jarjartask本身继承于内建的jar任务。通过fileset指定的class文件可以被打包起来,如果仅仅将其它项目的class文件内嵌到自己的项目中并不能解决Jar Hell问题,因为此时类文件依旧保持着原有的名字。 我们可以通过 zipfileset 指定将其它项目的文件包含到自己的项目发布中,为了描述重命名的需求JarJar提供了一个Pattern配置来实现。

<target name="jar" depends="compile">
    <taskdef name="jarjar" classname="com.tonicsystems.jarjar.JarJarTask"
        classpath="lib/jarjar.jar"/>
    <jarjar jarfile="dist/example.jar">
        <fileset dir="build/main"/>
        <!-- 包含一个第三方 jar 到项目中 -->
        <zipfileset src="lib/jaxen.jar"/>
        <!-- JarJar 提供了一个Pattern配置用来描述重命名-->
        <rule pattern="org.jaxen.**" result="org.example.@1"/>
    </jarjar>
</target>

在上述的实例中我们将jaxen.jar 打包到自己的发布中,并且将以 org.jaxen为开头的包及其子包的内容重命名到org.example之下。Pattern 中的**匹配任意有效包的子字符串,如果匹配单一的子包可以使用*表示并且通过.来分隔。@1表示第一个匹配,@2依次排列,@0可以用来表示整个匹配串。(这一块该怎么解释更明白呢?看Spring的配置吧!)

基于Gradle使用JarJar

dependencies {
    // Use jarjar.repackage in place of a dependency notation.
    compile jarjar.repackage {
        from 'com.google.guava:guava:18.0'

        classDelete "com.google.common.base.**"

        classRename "com.google.**" "org.private.google.@1"
    }
}

基于命令行使用JarJar

command-line

java -jar jarjar.jar [help]

help

java -jar jarjar.jar strings <cp>

Dumps all string literals in classpath

java -jar jarjar.jar find <level> <cp1> [<cp2>]

这个命令可以用来构建两个classpath 之间的依赖关系,level可以使class或者jar。如果不存在cp2,则表示使用cp1代替。

转换Jar

java -jar jarjar.jar process <rulesFile> <inJar> <outJar>

这个命令可以讲inJar中的内容转移到outJar中,outJar内容将全部被删除。

classpath属性是一组冒号或者分号分隔的文件夹、jar、zip文件。rules支持Mustang-style通配符描述。

在Maven中实现JarJar功能

Maven提供了一个 Plugin 来实现JarJar功能

<dependency>
  <groupId>org.sonatype.plugins</groupId>
  <artifactId>jarjar-maven-plugin</artifactId>
  <version>${jarjar-version}</version>
</dependency>
<!-- ...... -->
<plugin>
<groupId>org.sonatype.plugins</groupId>
<artifactId>jarjar-maven-plugin</artifactId>
<executions>
  <execution>
    <phase>package</phase>
    <goals>
      <goal>jarjar</goal>
    </goals>
    <configuration>
      <includes>
        <include>asm:asm</include>
        <include>org.sonatype.sisu.inject:cglib</include>
      </includes>
      <rules>
        <rule>
          <pattern>org.objectweb.asm.**</pattern>
          <result>com.google.inject.internal.asm.@1</result>
        </rule>
        <rule>
          <pattern>net.sf.cglib.**</pattern>
          <result>com.google.inject.internal.cglib.@1</result>
        </rule>
        <keep>
          <pattern>com.google.inject.**</pattern>
        </keep>
      </rules>
    </configuration>
  </execution>
</executions>
</plugin>

本文采用CC BY-SA许可发布,您可以自由的转载分享。

转载请保留出处 BeanMr.
http://blog.beanmr.com/2015/01/08/Export-Jar-Gracefully/