Обработка изменений в зависимых сторонних библиотеках - PullRequest
13 голосов
/ 10 февраля 2012

У меня есть проект, который зависит от нескольких сторонних библиотек, сам проект упакован в виде 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)), который обратно совместим, это более общий вопрос.

Ответы [ 7 ]

12 голосов
/ 13 февраля 2012

Вы спрашиваете, что является «лучшей практикой» для этой проблемы. Я собираюсь предположить, что под «этой проблемой» вы подразумеваете проблему обновлений сторонних библиотек, а именно, эти два вопроса:

  1. Когда вы должны обновить?

  2. Что вы должны сделать, чтобы защитить себя от неудачных обновлений (например, об ошибке в кодексе commons-codec, упомянутой в вашем примере)?

Чтобы ответить на первый вопрос, «когда вам следует обновиться?», В промышленности существует множество стратегий. В большей части коммерческого мира Java я считаю, что в настоящее время доминирующая практика заключается в том, что «вы должны обновиться, когда будете к этому готовы». Другими словами, как разработчик, вы сначала должны осознать, что доступна новая версия библиотеки (для каждой из ваших библиотек!), Затем вам нужно интегрировать ее в свой проект, и вы тот, кто делает окончательный вариант решение go / no-go, основанное на вашем собственном тестовом стенде - юнит, регрессия, ручное тестирование и т. д. - что бы вы ни делали, чтобы гарантировать качество. Maven облегчает этот подход (я называю это «закреплением» версии), делая несколько версий самых популярных библиотек доступными для автоматической загрузки в вашу систему сборки, и молча поощряя эту традицию «закрепления».

Но существуют и другие практики, например, в дистрибутиве Debian Linux теоретически возможно делегировать большую часть этой работы сопровождающим пакетов Debian. Вы просто наберете свой уровень комфорта в соответствии с 4 уровнями, которые предоставляет Debian, выбирая новизну вместо риска, или наоборот. Доступные Debian 4 уровня: OLDSTABLE, STABLE, TESTING, UNSTABLE. Unstable является удивительно стабильным, несмотря на свое название, и OLDSTABLE предлагает библиотеки, которые могут устареть на три года по сравнению с последними и лучшими версиями, доступными на их исходных веб-сайтах проекта «upstream».

Что касается 2-го вопроса, как защитить себя, я думаю, что текущая «лучшая практика» в отрасли двояка: выбирайте свои библиотеки на основе репутации (у Apache, как правило, неплохо), и немного подождите, прежде чем обновлять Не всегда спешите быть на последнем и величайшем. Возможно, выберите публичный выпуск библиотеки, который уже доступен через 3–6 месяцев, в надежде, что любые критические ошибки были устранены и исправлены с момента первоначального выпуска.

Вы можете пойти дальше, написав тесты JUnit, которые специально защищают поведение, на которое вы полагаетесь в своих зависимостях. Таким образом, когда вы отключите более новую версию библиотеки, ваш JUnit сразу же потерпит неудачу, предупреждая вас о проблеме. Но я не вижу много людей, делающих это, по моему опыту. И часто трудно понять точное поведение, на которое вы полагаетесь.

И, кстати, я Юлий, парень, ответственный за эту ошибку! Пожалуйста, примите мои извинения за эту проблему. Вот почему я думаю, что это произошло. Я буду говорить только за себя. Чтобы узнать, что думают другие члены команды apache commons-codec, вам нужно спросить их сами (например, ggregory, sebb).

  1. Когда я работал над Base64 в версиях 1.4 и 1.5, я был очень сосредоточен на основной проблеме Base64, а именно на кодировании двоичных данных в младшем 127 ASCIi и декодировании их обратно в двоичный .

  2. Так что, на мой взгляд (и здесь я ошибся), разница между "aGk = \ r \ n" и "aGk =" не имеет значения. Они оба декодируют с одинаковым двоичным результатом!

  3. Но, подумав об этом в более широком смысле после прочтения вашей публикации в стеке, я понимаю, что, вероятно, существует очень популярный вариант использования, который я никогда не рассматривал. То есть проверка пароля по таблице зашифрованных паролей в базе данных. В этом случае вы, вероятно, делаете следующее:

    // 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, версию с ошибкой, и исправить эту ошибку, по сути, означает, что нужно поиметь людей, которые использовали версию 1.5 вместо версии 1.4, где вы это сделали.Но я постараюсь разместить некоторую документацию на веб-сайте apache, чтобы предупредить людей.Спасибо за упоминание этого здесь о переполнении стека (я прав насчет варианта использования?).

ps.Я думал, что решение Пола Грайма было довольно изящным, но я подозреваю, что оно основано на том, что проекты помещают информацию о версии в файл META-INF/MANIFEST.MF Jar.Я думаю, что все библиотеки Apache Java делают это, но другие проекты не могут.Этот подход - хороший способ привязать себя к версиям во время сборки: вместо того, чтобы понять, что вы зависите от "\ r \ n", и написать JUnit, который защищает от этого, вы можете вместо этого написать гораздо более простой JUnit:assertTrue(desiredLibVersion.equals(actualLibVersion)).

(Предполагается, что библиотеки времени выполнения не меняются по сравнению с библиотеками времени сборки!)

6 голосов
/ 10 февраля 2012
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.

1 голос
/ 12 февраля 2012

Асаф, я решаю эту проблему с помощью Maven . Maven имеет хорошую поддержку версий для всех артефактов, которые вы используете в своем проекте. Кроме того, я использую превосходный плагин Maven Shade , который дает вам возможность упаковать все сторонние библиотеки (артефакты maven) в один файл JAR, готовый к развертыванию. Все остальные решения просто уступают - я говорю из своего личного опыта - я был там, сделал это ... Даже написал свой собственный менеджер плагинов и т. Д. Используйте Maven, это мой дружеский совет.

1 голос
/ 12 февраля 2012

Вы можете вычислить сумму 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()){
        ....        
    }    
}
0 голосов
/ 18 февраля 2012

Чтобы решить вашу проблему, я думаю, что лучше всего использовать контейнер OSGi , чтобы вы могли выбрать свою версию сторонней зависимости, и другие библиотеки могут безопасно использовать другую версию без каких-либо конфликтов.

Если вы не можете полагаться на контейнер OSGi, вы можете использовать версию реализации в MANIFEST.MF

Maven - отличный инструмент, но он не может решить вашу проблему в одиночку.

0 голосов
/ 13 февраля 2012

Я бы создал 2+ разных версии библиотеки, чтобы дополнить соответствующую версию сторонней библиотеки и предоставить руководство, которое использовать.Вероятно, напишите правильный пом для этого.

0 голосов
/ 12 февраля 2012

замена новой строки пустой строкой может быть решением?

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