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

Asa*_*saf 13 java dependencies

我有一个依赖于几个第三方库的项目,项目本身被打包为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
Run Code Online (Sandbox Code Playgroud)

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

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

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

Jul*_*eau 12

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

  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)).

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


Pau*_*ime 6

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());
    }
}
Run Code Online (Sandbox Code Playgroud)

产生(对于v1.6):

'aGk='
package org.apache.commons.codec.binary, Commons Codec, version 1.6
specificationVersion: 1.6
implementationVersion: 1.6
Run Code Online (Sandbox Code Playgroud)

产生(对于v1.4):

'aGk=
'
package org.apache.commons.codec.binary, Commons Codec, version 1.4
specificationVersion: 1.4
implementationVersion: 1.4
Run Code Online (Sandbox Code Playgroud)

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

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

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