我想重新打包apache的httpclient lib,用一个Android应用程序(如https://code.google.com/p/httpclientandroidlib/,但使用HttpClient 4.3.1)发送它
因此,我手工使用httpclient 4.3.1 jar(包括其所有依赖项)并使用jarjar重新打包它:
x@x$: cd libs && for f in *.jar; do java -jar ../jarjar-1.4.jar process ../rules.txt $f out/my-$f; done
与 rules.txt :
rule org.apache.http.** my.repackaged.org.apache.http.@1
然后我用ant将输出放在一起:
<project name="MyProject" default="merge" basedir=".">
<target name="merge">
<zip destfile="my-org-apache-httpclient-4.3.1.jar">
<zipgroupfileset dir="libs/out" includes="*.jar"/>
</zip>
</target>
</project>
我可以使用该文件来开发和测试我的应用程序,但如果我在android上部署它,它会抛出一个异常,因为它无法找到 my.package.org.apache.logging.whatEver
引用的 my.repackaged.org.apache.logging.log4j.something
.
所以,现在我想通过使用字节码操作来消除对commons-logging的任何依赖 . 这之前已经完成:http://sixlegs.com/blog/java/dependency-killer.html
但我想知道我是怎么做到的? org.apache.commons.logging.Log只有依赖项:
x$x$: java -jar jarjar-1.4.jar find jar my-org-apache-httpclient-4.3.1.jar commons-logging-1.1.3.jar
my/http/impl/execchain/ServiceUnavailableRetryExec -> org/apache/commons/logging/Log
my/http/impl/execchain/RetryExec -> org/apache/commons/logging/Log
my/http/impl/execchain/RedirectExec -> org/apache/commons/logging/Log
my/http/impl/execchain/ProtocolExec -> org/apache/commons/logging/Log
...
我认为要走的路是,删除这些依赖项并将其替换为自己的实现,就像他在这里所做的那样https://code.google.com/p/httpclientandroidlib/ . 因此,我创建了一个新的maven项目,只有一个具有 provided
范围的类,用于实现org.apache.commons.logging.Log接口的commons-logging,只删除 android.utils.Log
:
MyLog implements org.apache.commons.logging.Log {}
在包 my.log
中,我将其打包在my-log-1.0.0.jar中 . 我将该jar放入与重新打包的httpclient-jars相同的文件夹中,并使用上面提到的ant将所有包装在my-org-apache-httpclient-4.3.1.jar中 .
Approach 1
我试着再次使用jarjar:
java -jar jarjar-1.4.jar process rules2.txt my-org-apache-httpclient-4.3.1.jar my-org-apache-httpclient-4.3.1-without-logging-dep.jar
与 rules2.txt :
rule my.repackaged.commons.logging.** my.log.@1
但这不起作用 . 它仍然无法找到 my.package.org.apache.logging.whatEver
引用的 my.repackaged.org.apache.logging.log4j.something
的异常 .
Approach 2
我还尝试从最终jar中删除日志记录内容和/或重新打包my.repackaged.org.apache.log4j并记录到其原始包:
rules2.txt v2 :
rule my.repackaged.org.apache.log4j.** org.apache.log4j.@1
rule my.repackaged.org.apache.logging.** org.apache.logging.@1
但这仍然是抛出的重复: my.repackaged.org.apache.logging.log4j.something
由 my.package.org.apache.logging.whatEver
引用
QUESTION
如何杀死/替换commons-logging依赖项并摆脱异常?
1 回答
简介
如果程序依赖于库,则通常意味着它使用库的方法 . 因此,删除依赖关系不是一项简单的任务 . 您实际上想要删除程序所需的代码 - 至少是正式的 - .
删除依赖项有三种方法:
使 source code 适应不依赖于库并从头开始编译 .
修改 bytecode 以删除对项目所依赖的库的引用 .
操纵 runtime 以不需要依赖项 . 最简单的方法是重新创建所需的类并将它们放入jar文件中 .
这些方式都不是真的很漂亮 . 所有这些都需要大量的工作 . 没有保证没有副作用 .
解决方案
我将通过介绍用于解决问题的文件和步骤来描述我的解决方案 . 要重现,您将需要以下文件(在单个目录中):
lib/xxx-v.v.v.jar :库jar(httpclient和依赖项, excluding commons-logging-1.1.3.jar)
jarjar-1.4.jar :用于重新包装 jar
rules.txt :jarjar规则
build.xml :Ant构建配置
java/src/Log.java
java/src/LogFactory.java
do_everything.sh
而已 . 打开一个控制台并执行
这将创建一个包含单个jar文件的文件夹“out”和“httpclient-4.3.1.jar”,它是最终的独立工作jar文件 . 那么,我们刚刚做了什么?
重新包装的httpclient(现在在
my.http
中)修改库以使用
my.logging
而不是org.apache.commons.logging
编译库(
my.logging.Log
和my.logging.LogFactory
)要使用的必需类 .将重新打包的库和已编译的类合并到一个jar文件httpclient-4.3.1.jar中 .
很简单,不是吗?只需逐行阅读shell脚本即可发现单个步骤 . 要检查是否已删除所有依赖项,您可以运行
我尝试使用SE7和Android 4.4生成的jar文件,它在两种情况下均有效(请参阅下面的备注) .
类文件版本
每个类文件都有一个主要版本和一个次要版本(都取决于编译器) . Android SDK要求类文件的主要版本小于0x33(因此所有内容都是1.7 / JDK 7之前的版本) . 我将
target="1.5"
属性添加到antjavac
任务中,因此生成的类文件的主要版本为0x31,因此可以包含在您的Android应用程序中 .替代(字节码操作)
你很幸运记录(几乎总是)是单向操作 . 它几乎不会引起影响主程序的副作用 . 这意味着删除公共日志应该是可能的,因为它不会影响程序的功能 .
我选择了你在问题中建议的第二种方式,字节码操作 . 这个概念基本上就是这个(A是httpclient,B是commons-logging):
如果A的方法的返回类型是B的一部分,则返回类型将更改为
java.lang.Object
.如果有的话A方法的参数的类型是B的一部分,参数类型将更改为
java.lang.Object
.完全删除属于B的方法的调用 . 插入
pop
和常量指令以修复VM堆栈 .属于B的类型从A调用的方法的描述符中删除 . 这需要处理目标类(包含被调用方法的类) . 属于B的所有对象类型将替换为
java.lang.Object
.删除了尝试访问属于B的类字段的指令 . 插入
pop
和常量指令以修复VM堆栈 .如果方法尝试访问属于B的类型的字段,则指令引用的字段签名将更改为
java.lang.Object
. 这需要处理目标类(包含访问字段的类) .修改了B中包含但属于A类的字段,以使其类型为
java.lang.Object
.正如您所看到的,这背后的想法是用
java.lang.Object
替换所有引用的类,并删除对属于commons-logging的类成员的所有访问 .我不知道这是否可靠,并且在应用操纵器后我没有测试库 . 但是从我所看到的(反汇编的类文件和加载类文件时没有VM错误)我相当确定代码是有效的 .
我试图记录程序的几乎所有内容 . 它使用ASM Tree API,它提供了对类文件结构的相当简单的访问 . 并且 - 为了避免不必要的负面评论 - 这是"quick 'n' dirty"代码 . 我没有真正测试它,我打赌有更快的字节码操作方式 . 但是这个程序似乎完全符合OP 's needs and that'所做的一切 .