简体   繁体   English

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

[英]Handling changes in dependent 3rd party libraries

I have a project which depends on several 3rd party libraries, the project itself is packaged as a jar and distributed to other developers as a library. 我有一个依赖于几个第三方库的项目,项目本身被打包为jar并作为库分发给其他开发人员。 Those developers add the dependencies to their classpath and use my library in their code. 这些开发人员将依赖项添加到其类路径中,并在其代码中使用我的库。

Recently I had an issue with one of the 3rd party dependencies, the apache commons codec libary, The problem is this: 最近我遇到了第三方依赖项之一的问题,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

As you can see the output of the method has changed with the minor version bump. 正如您所看到的,方法的输出随着次要版本的更改而发生了变化。

My question is, I don't want to force the user of my library to a specific minor version of a 3rd party library. 我的问题是,我不想强​​迫我的图书馆用户使用第三方图书馆的特定次要版本。 Assuming I know about the change to the dependent library, is there anyway in which I can recognize which library version is being included in the classpath and behave accordingly? 假设我知道对依赖库的更改,无论如何我可以识别哪个库版本被包含在类路径中并且相应地表现? or alternatively, what is considered to be the best practice for these kind of scenarios? 或者,什么被认为是这种情景的最佳做法?

PS - I know that for the above example I can just use new String(Base64.encodeBase64(data, false)) which is backwards compatible, this is a more general question. PS - 我知道对于上面的例子,我可以使用向后兼容的new String(Base64.encodeBase64(data, false)) ,这是一个更普遍的问题。

You ask what is the "best practice" for this problem. 你问这个问题的“最佳实践”是什么。 I'm going to assume that by "this problem" you mean the problem of 3rd party library upgrades, and specifically, these two questions: 我将假设“这个问题”是指第三方库升级的问题,具体而言,这两个问题:

  1. When should you upgrade? 你什么时候升级?

  2. What should you do to protect yourself against bad upgrades (like the commons-codec bug mentioned in your example)? 你应该做些什么来保护自己免受不良升级(比如你的例子中提到的commons-codec bug)?

To answer the first question, "when should you upgrade?," many strategies exist in industry. 要回答第一个问题,“你什么时候应该升级?”,许多策略都存在于行业中。 In the majority of the commercial Java world I believe the current dominant practice is "you should upgrade when you are ready to." 在大多数商业Java世界中,我认为目前的主导做法是“当你准备好时,你应该进行升级。” In other words, as the developer, you first need to realize that a new version of a library is available (for each of your libraries!), you then need to integrate it into your project, and you are the one who makes the final go/no-go decision based on your own test bed --- junit, regression, manual testing, etc... whatever it is you do to ensure quality. 换句话说,作为开发人员,您首先需要意识到可以使用新版本的库(对于您的每个库!),然后您需要将其集成到您的项目中,并且您是最终的人根据您自己的测试床进行/不进行决定--- junit,回归,手动测试等...无论您做什么来确保质量。 Maven facilitates this approach (I call it version "pinning") by making multiple versions of most popular libraries available for automatic download into your build system, and by tacitly fostering this "pinning" tradition. Maven通过使多个版本的大多数流行库可以自动下载到您的构建系统中,并通过默认促进这种“固定”传统来促进这种方法(我称之为“固定”版本)。

But other practices do exist, for example, within the Debian Linux distribution it is theoretically possible to delegate a lot of this work to the Debian package maintainers. 但是其他实践确实存在,例如,在Debian Linux发行版中,理论上可以将大量此类工作委托给Debian软件包维护者。 You would simply dial in your comfort level according to the 4 levels Debian makes available, choosing newness over risk, or vice versa. 您只需根据Debian提供的4个级别拨打您的舒适级别,选择新风险,或反之亦然。 The 4 levels Debian makes available are: OLDSTABLE, STABLE, TESTING, UNSTABLE. Debian提供的4个级别是:OLDSTABLE,STABLE,TESTING,UNSTABLE。 Unstable is remarkably stable, despite its name, and OLDSTABLE offers libraries that may as much as 3 years out of date compared to the latest-and-greatest versions available on their original "upstream" project websites. 不稳定是非常稳定的,尽管它的名字,OLDSTABLE提供的库可能比他们原来的“上游”项目网站上提供的最新和最好的版本长达3年。

As for the 2nd question, how to protect yourself, I think the current 'best practice' in industry is twofold: choose your libraries based on reputation (Apache's is generally pretty good), and wait a little while before upgrading, eg, don't always rush to be on the latest-and-greatest. 至于第二个问题,如何保护自己,我认为目前行业中的“最佳实践”是双重的:根据声誉选择你的库(Apache通常都很好),并在升级之前等待一段时间,例如,don'总是急于成为最新的和最伟大的。 Maybe choose a public release of the library that has already been available 3 to 6 months, in the hope that any critical bugs have been flushed out and patched since the initial release. 也许选择已经有3到6个月可用的库的公开发布,希望自最初发布以来任何关键错误都已被刷新和修补。

You could go farther, by writing JUnit tests that specifically protect the behaviours you rely on in your dependencies. 通过编写专门保护依赖项中依赖的行为的JUnit测试,您可以更进一步。 That way, when you bring down the newer version of a library, your JUnit would fail right away, warning you of the problem. 这样,当您关闭较新版本的库时,您的JUnit会立即失败,并警告您该问题。 But I don't see a lot of people doing that, in my experience. 但根据我的经验,我没有看到很多人这样做。 And it's often difficult to be aware of the precise behaviour you are relying on. 而且通常很难意识到您所依赖的精确行为。

And, by the way, I'm Julius, the guy responsible for this bug! 顺便说一句,我是Julius,负责这个bug的人! Please accept my apologies for this problem. 请接受我对此问题的歉意。 Here's why I think it happened. 这就是为什么我认为它发生了。 I will speak only for myself. 我只会为自己说话。 To find out what others on the apache commons-codec team think, you'll have to ask them yourself (eg, ggregory, sebb). 要找出apache commons-codec团队认为的其他人,你必须自己问问他们(例如,ggregory,sebb)。

  1. When I was working on Base64 in versions 1.4 and 1.5, I was very much focused on the main problem of Base64, that is, encoding binary data into the lower-127 ASCIi, and the decoding it back to binary. 当我在版本1.4和1.5中使用Base64时,我非常关注Base64的主要问题,即将二进制数据编码到低127的ASCIi中,并将其解码回二进制。

  2. So in my mind (and here's where I went wrong) the difference between "aGk=\\r\\n" and "aGk=" is immaterial. 所以在我看来(这是我出错的地方)“aGk = \\ r \\ n”和“aGk =”之间的区别并不重要。 They both decode to the same binary result! 它们都解码为相同的二进制结果!

  3. But thinking about it in a broader sense after reading your stackoverflow posting here, I realize there is probably a very popular usecase that I never considered. 但是在阅读了你的stackoverflow帖子后,在更广泛的意义上考虑它,我意识到可能有一个我从未考虑过的非常流行的用例。 That is, password checking against a table of encrypted passwords in a database. 也就是说,对数据库中的加密密码表进行密码检查。 In that usecase you probably do the following: 在该用例中,您可能会执行以下操作:

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

So of course this usecase fails if commons-codec.jar changes its encoding behaviour, even in immaterial ways according to the base64 spec. 所以当然,如果commons-codec.jar改变了它的编码行为,那么这个用例就会失败,即使是根据base64规范的非物质方式。 I'm very sorry! 我非常抱歉!

I think even with all of the "best-practices" I spelled out at the beginning of this post, there's still a high probability of getting screwed on this one. 我认为即使我在本文开头所阐述的所有“最佳实践”,仍然很有可能被搞砸了。 Debian Testing already contains commons-codec-1.5, the version with the bug, and to fix this bug essentially means screwing people who used version 1.5 instead of version 1.4 where you did. Debian Testing已经包含了commons-codec-1.5,带有bug的版本,并且修复这个bug本质上意味着搞砸使用1.5版而不是版本1.4的人。 But I will try to put some documentation on the apache website to warn people. 但我会尝试在apache网站上放一些文档来警告人们。 Thanks for mentioning it here on stack-overflow (am I right about the usecase?). 谢谢你在堆栈溢出这里提到它(我是否正确使用usecase?)。

ps. PS。 I thought Paul Grime 's solution was pretty neat, but I suspect it relies on projects pushing version info in the the Jar's META-INF/MANIFEST.MF file. 我认为Paul Grime的解决方案非常简洁,但我怀疑它依赖于在Jar的META-INF/MANIFEST.MF文件中推送版本信息的项目。 I think all Apache Java libraries do this, but other projects might not. 我认为所有的Apache Java库都可以做到这一点,但其他项目可能没有。 The approach is a nice way to pin yourself to versions at build-time though: instead of realizing that you depend on the "\\r\\n", and writing the JUnit that protects against that, you can instead write a much easier JUnit: assertTrue(desiredLibVersion.equals(actualLibVersion)) . 这种方法是一种很好的方法,可以在构建时将自己固定到版本:不是意识到你依赖于“\\ r \\ n”,而是编写了防止它的JUnit,你可以编写一个更简单的JUnit: assertTrue(desiredLibVersion.equals(actualLibVersion))

(This assumes run-time libs don't change compared to build-time libs!) (这假设运行时库与构建时库相比没有变化!)

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());
    }
}

Produces (for v1.6): 产生(对于v1.6):

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

Produces (for v1.4): 产生(对于v1.4):

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

So you could use the package object to test. 所以你可以用package对象来测试。

But I would say that it's a bit naughty for the API to have changed the way it did. 但是我会说API改变它的方式有点顽皮。

EDIT Here is the reason for the change - https://issues.apache.org/jira/browse/CODEC-99 . 编辑以下是更改的原因 - https://issues.apache.org/jira/browse/CODEC-99

You could calculate a md5 sum of the actual class file and compare it to the expected. 您可以计算实际类文件的md5总和,并将其与预期值进行比较。 Could work like this: 可以像这样工作:

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();

Or maybe you could iterate over the filenames in the classpath. 或者您可以迭代类路径中的文件名。 Of course this only works, if the devs use the original filenames. 当然,只有开发人员使用原始文件名时,这才有效。

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

Asaf, I solve this problem by using Maven . Asaf,我使用Maven解决了这个问题。 Maven has nice versioning support for all artifacts you use in your project. Maven对您在项目中使用的所有工件提供了很好的版本控制支持。 On top of that, I use the excellent Maven Shade Plugin which gives you ability to package all 3rd party libraries (maven artifacts) in a single JAR file, ready for deployment. 最重要的是,我使用了优秀的Maven Shade插件 ,它允许您将所有第三方库(maven工件)打包在一个JAR文件中,可以进行部署。 All other solutions are just inferior - I am talking from my personal experience - I've been there, done that... Even wrote my own plugin-manager, etc. Use Maven, that is my friendly advice. 所有其他的解决方案都是低劣的 - 我正在谈论我的个人经历 - 我去过那里,完成了......甚至写了我自己的插件管理器等。使用Maven,这是我友好的建议。

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

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

I would create 2+ different versions of a library to complement appropriate third party library version and provide manual which one to use. 我将创建2个以上不同版本的库,以补充适当的第三方库版本,并提供手册使用哪个。 Probably write correct pom for it. 可能为它写正确的pom。

To resolve your problem I think the best way is to use a OSGi container, so you can choose your version of the 3rd party dependency and other libraries can safely use the other version without any conflict. 要解决您的问题,我认为最好的方法是使用OSGi容器,这样您就可以选择第三方依赖项的版本,其他库可以安全地使用其他版本而不会产生任何冲突。

If you cannot rely on a OSGi container then you can use the implementation version in the MANIFEST.MF 如果您不能依赖OSGi容器,那么您可以使用MANIFEST.MF中的实现版本

Maven is a great tool, but cannot alone resolve your problem. Maven是一个很棒的工具,但不能单独解决你的问题。

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

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