繁体   English   中英

处理依赖的第三方库中的更改

[英]Handling changes in dependent 3rd party libraries

我有一个依赖于几个第三方库的项目,项目本身被打包为jar并作为库分发给其他开发人员。 这些开发人员将依赖项添加到其类路径中,并在其代码中使用我的库。

最近我遇到了第三方依赖项之一的问题,apache commons编解码器库,问题是:

byte[] arr = "hi".getBytes();
// Codec Version 1.4
Base64.encodeBase64String(arr) == "aGk=\r\n" // this is true

// Codec Version 1.6
Base64.encodeBase64String(arr) == "aGk=" // this is true

正如您所看到的,方法的输出随着次要版本的更改而发生了变化。

我的问题是,我不想强​​迫我的图书馆用户使用第三方图书馆的特定次要版本。 假设我知道对依赖库的更改,无论如何我可以识别哪个库版本被包含在类路径中并且相应地表现? 或者,什么被认为是这种情景的最佳做法?

PS - 我知道对于上面的例子,我可以使用向后兼容的new String(Base64.encodeBase64(data, false)) ,这是一个更普遍的问题。

你问这个问题的“最佳实践”是什么。 我将假设“这个问题”是指第三方库升级的问题,具体而言,这两个问题:

  1. 你什么时候升级?

  2. 你应该做些什么来保护自己免受不良升级(比如你的例子中提到的commons-codec bug)?

要回答第一个问题,“你什么时候应该升级?”,许多策略都存在于行业中。 在大多数商业Java世界中,我认为目前的主导做法是“当你准备好时,你应该进行升级。” 换句话说,作为开发人员,您首先需要意识到可以使用新版本的库(对于您的每个库!),然后您需要将其集成到您的项目中,并且您是最终的人根据您自己的测试床进行/不进行决定--- junit,回归,手动测试等...无论您做什么来确保质量。 Maven通过使多个版本的大多数流行库可以自动下载到您的构建系统中,并通过默认促进这种“固定”传统来促进这种方法(我称之为“固定”版本)。

但是其他实践确实存在,例如,在Debian Linux发行版中,理论上可以将大量此类工作委托给Debian软件包维护者。 您只需根据Debian提供的4个级别拨打您的舒适级别,选择新风险,或反之亦然。 Debian提供的4个级别是:OLDSTABLE,STABLE,TESTING,UNSTABLE。 不稳定是非常稳定的,尽管它的名字,OLDSTABLE提供的库可能比他们原来的“上游”项目网站上提供的最新和最好的版本长达3年。

至于第二个问题,如何保护自己,我认为目前行业中的“最佳实践”是双重的:根据声誉选择你的库(Apache通常都很好),并在升级之前等待一段时间,例如,don'总是急于成为最新的和最伟大的。 也许选择已经有3到6个月可用的库的公开发布,希望自最初发布以来任何关键错误都已被刷新和修补。

通过编写专门保护依赖项中依赖的行为的JUnit测试,您可以更进一步。 这样,当您关闭较新版本的库时,您的JUnit会立即失败,并警告您该问题。 但根据我的经验,我没有看到很多人这样做。 而且通常很难意识到您所依赖的精确行为。

顺便说一句,我是Julius,负责这个bug的人! 请接受我对此问题的歉意。 这就是为什么我认为它发生了。 我只会为自己说话。 要找出apache commons-codec团队认为的其他人,你必须自己问问他们(例如,ggregory,sebb)。

  1. 当我在版本1.4和1.5中使用Base64时,我非常关注Base64的主要问题,即将二进制数据编码到低127的ASCIi中,并将其解码回二进制。

  2. 所以在我看来(这是我出错的地方)“aGk = \\ r \\ n”和“aGk =”之间的区别并不重要。 它们都解码为相同的二进制结果!

  3. 但是在阅读了你的stackoverflow帖子后,在更广泛的意义上考虑它,我意识到可能有一个我从未考虑过的非常流行的用例。 也就是说,对数据库中的加密密码表进行密码检查。 在该用例中,您可能会执行以下操作:

// a.  store user's password in the database
    //     using encryption and salt, and finally,
    //     commons-codec-1.4.jar (with "\r\n").
    //

    // b.  every time the user logs in, encrypt their
    //     password using appropriate encryption alg., plus salt,
    //     finally base64 encode using latest version of commons-codec.jar,
    //     and then check against encrypted password in the database
    //     to see if it matches.

所以当然,如果commons-codec.jar改变了它的编码行为,那么这个用例就会失败,即使是根据base64规范的非物质方式。 我非常抱歉!

我认为即使我在本文开头所阐述的所有“最佳实践”,仍然很有可能被搞砸了。 Debian Testing已经包含了commons-codec-1.5,带有bug的版本,并且修复这个bug本质上意味着搞砸使用1.5版而不是版本1.4的人。 但我会尝试在apache网站上放一些文档来警告人们。 谢谢你在堆栈溢出这里提到它(我是否正确使用usecase?)。

PS。 我认为Paul Grime的解决方案非常简洁,但我怀疑它依赖于在Jar的META-INF/MANIFEST.MF文件中推送版本信息的项目。 我认为所有的Apache Java库都可以做到这一点,但其他项目可能没有。 这种方法是一种很好的方法,可以在构建时将自己固定到版本:不是意识到你依赖于“\\ r \\ n”,而是编写了防止它的JUnit,你可以编写一个更简单的JUnit: assertTrue(desiredLibVersion.equals(actualLibVersion))

(这假设运行时库与构建时库相比没有变化!)

package stackoverflow;

import org.apache.commons.codec.binary.Base64;

public class CodecTest {
    public static void main(String[] args) {
        byte[] arr = "hi".getBytes();
        String s = Base64.encodeBase64String(arr);
        System.out.println("'" + s + "'");
        Package package_ = Package.getPackage("org.apache.commons.codec.binary");
        System.out.println(package_);
        System.out.println("specificationVersion: " + package_.getSpecificationVersion());
        System.out.println("implementationVersion: " + package_.getImplementationVersion());
    }
}

产生(对于v1.6):

'aGk='
package org.apache.commons.codec.binary, Commons Codec, version 1.6
specificationVersion: 1.6
implementationVersion: 1.6

产生(对于v1.4):

'aGk=
'
package org.apache.commons.codec.binary, Commons Codec, version 1.4
specificationVersion: 1.4
implementationVersion: 1.4

所以你可以用package对象来测试。

但是我会说API改变它的方式有点顽皮。

编辑以下是更改的原因 - https://issues.apache.org/jira/browse/CODEC-99

您可以计算实际类文件的md5总和,并将其与预期值进行比较。 可以像这样工作:

String classname = "java.util.Random"; //fill in the your class
MessageDigest digest = MessageDigest.getInstance("MD5");
Class test = Class.forName(classname);
InputStream in = test.getResourceAsStream("/" + classname.replace(".", "/") + ".class");
byte[] buffer = new byte[8192];
int read = 0;

while ((read = in.read(buffer)) > 0) {
    digest.update(buffer, 0, read);
}
byte[] md5sum = digest.digest();
BigInteger bigInt = new BigInteger(1, md5sum);
String output = bigInt.toString(16);
System.out.println(output);

in.close();

或者您可以迭代类路径中的文件名。 当然,只有开发人员使用原始文件名时,这才有效。

String classpath = System.getProperty("java.class.path");
for(String path:classpath.split(";")){
    File o = new File(path);
    if(o.isDirectory()){
        ....        
    }    
}

Asaf,我使用Maven解决了这个问题。 Maven对您在项目中使用的所有工件提供了很好的版本控制支持。 最重要的是,我使用了优秀的Maven Shade插件 ,它允许您将所有第三方库(maven工件)打包在一个JAR文件中,可以进行部署。 所有其他的解决方案都是低劣的 - 我正在谈论我的个人经历 - 我去过那里,完成了......甚至写了我自己的插件管理器等。使用Maven,这是我友好的建议。

用空字符串替换换行符可能是一个解决方案吗?

Base64.encodeBase64String(arr).replace("\r\n","");

我将创建2个以上不同版本的库,以补充适当的第三方库版本,并提供手册使用哪个。 可能为它写正确的pom。

要解决您的问题,我认为最好的方法是使用OSGi容器,这样您就可以选择第三方依赖项的版本,其他库可以安全地使用其他版本而不会产生任何冲突。

如果您不能依赖OSGi容器,那么您可以使用MANIFEST.MF中的实现版本

Maven是一个很棒的工具,但不能单独解决你的问题。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM