使用JarJar重封装减少发布包的依赖
Spring框架体积庞大、功能繁杂但是它的第三方依赖仅仅只有Commons Log
这是如何做到的呢?
其实在Spring的实现中也大量了使用cglib
,asm
等工具包,但是 Spring 并没有直接引入依赖,而是采用将某个版本的 Jar 重新打包到自己的 package 之下的方式引入依赖。这相当于将工具包的代码拷贝到自己的项目中,使工具包里面所有类的包名都在自己的命名空间之下,从而避免了自己和其它依赖共同工具包项目之间的冲突。如果真的通过拷贝源文件实现重新发包,恐怕这个修改会非常繁琐而且容易出错。
通过 Spring 的 API 文档可以清楚的看到这一点:
Spring使用了Jar Jar Links实现这个功能。
build.gradle
task cglibRepackJar(type: Jar) { repackJar -> repackJar.baseName = "spring-cglib-repack" repackJarversion = cglibVersion doLast() { projectant { taskdef name: "jarjar", classname: "com.tonicsystems.jarjar.JarJarTask"classpath: configurationsjarjarasPath jarjardestfile: repackJararchivePath{ configurationscglibeach { originalJar -> zipfilesetsrc: originalJar) } // repackage net.sf.cglib => org.springframework.cglib rulepattern: "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"org.objectweb.asm.**""org.springframework.asm.@1") } } } }
JarJar介绍
JarJarLinks
可以很方便的重新打包并封装到自己的发布中,这样做有两大好处:
- 便捷的创建一个无依赖的单一文件的发布
- 避免自身对特定版本包的依赖造成的与其它程序冲突
JarJar
包含一个继承于内建jar
任务的Ant Task
完成代码正常的打包工作,通过zipfileset
元素指定内嵌的jar 文件,另外添加了一个新的规则配置用来描述内嵌 jar 文件的重命名规则。JarJar使用ASM
进行bytecode
转换方式来实现变更reference
操作,并且提供一个特殊的handling
来迁移资源文件和进行字符串字面量的转换工作。
JarJar应用示例
基于Ant使用JarJar
一般情况下我们在Ant
会引入如下的task
使用JarJarLinks
我们可以使用以下配置代替上面功能,因为jarjar
task
本身继承于内建的jar
任务。通过fileset指定的class
文件可以被打包起来,如果仅仅将其它项目的class
文件内嵌到自己的项目中并不能解决Jar Hell
问题,因为此时类文件依旧保持着原有的名字。 我们可以通过 zipfileset 指定将其它项目的文件包含到自己的项目发布中,为了描述重命名的需求JarJar
提供了一个Pattern配置来实现。
<taskdef "jarjar" classname="com.tonicsystems.jarjar.JarJarTask" classpath="lib/jarjar.jar"<jarjar /> <!-- 包含一个第三方 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
@H_502_207@dependencies { // Use jarjar.repackage in place of a dependency notation. compile jarjarrepackage { from 'com.google.guava:guava:18.0' classDelete "com.google.common.base.**" classRename "com.google.**" "org.private.google.@1" }基于命令行使用JarJar
@H_502_207@java -jar jarjar.jar [help]command-line
@H_502_207@java -jar jarjar.jar strings <cp>help
@H_502_207@java -jar jarjar.jar find <level> <cp1> [<cp2>]Dumps all string literals in classpath
这个命令可以用来构建两个classpath 之间的依赖关系,level
可以使class
或者jar
。如果不存在cp2
,则表示使用cp1
代替。 >转换Jar
这个命令可以讲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> </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> <pattern>net.sf.cglib.**<result>com.google.inject.internal.cglib.@1<keep> <pattern>com.google.inject.**</pattern> </keep> </rules> </configuration> </execution> </executions> </plugin>
限于个人技术能力、见识限制,还望各位看官不吝赐教。
转载请保留:http://blog.beanmr.com/2015/01/08/Export-Jar-Gracefully/