簡體   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