处理从属第三方库中的更改

2022-09-04 19:24:53

我有一个项目依赖于几个第三方库,项目本身被打包为一个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

如您所见,该方法的输出已随次要版本凸起而变化。

我的问题是,我不想强迫我的库的用户使用第三方库的特定次要版本。假设我知道对依赖库的更改,那么无论如何,我是否可以识别类路径中包含的库版本并相应地运行?或者,对于这些类型的方案,什么被认为是最佳实践?

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


答案 1

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

  1. 何时应升级?

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

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

但是其他实践确实存在,例如,在 Debian Linux 发行版中,理论上可以将大量工作委托给 Debian 软件包维护者。您只需根据 Debian 提供的 4 个级别来调整您的舒适度,选择新颖性而不是风险,反之亦然。Debian 提供的 4 个级别是:OLDSTABLE、STABLE、TEST、UNSTABLE。尽管它的名字叫Unstable,但它非常稳定,OLDSTABLE提供的库,与原始“上游”项目网站上提供的最新和最好的版本相比,可能要过时3年。

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

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

顺便说一句,我是朱利叶斯,负责这个错误的人!请接受我对这个问题的歉意。这就是我认为它发生的原因。我只为自己说话。要了解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 测试已经包含了 commons-codec-1.5,这个版本有这个 bug,修复这个 bug 本质上意味着把那些使用 1.5 版而不是 1.4 版的人搞砸了。但是我会尝试在apache网站上放一些文档来警告人们。感谢您在堆栈溢出中提到它(我对用例的看法正确吗?

我认为Paul Grime的解决方案非常整洁,但我怀疑它依赖于在Jar文件中推送版本信息的项目。我认为所有的Apache Java库都这样做,但其他项目可能不会。不过,这种方法是在构建时将自己固定在版本上的一种很好的方法:与其意识到您依赖于“\r\n”,并编写了防止这种情况的JUnit,不如编写一个更容易的JUnit:。META-INF/MANIFEST.MFassertTrue(desiredLibVersion.equals(actualLibVersion))

(这假设运行时库与构建时库相比不会更改!


答案 2
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

因此,您可以使用包对象进行测试。

但我想说的是,API改变它的方式有点调皮。

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


推荐