Обработка разных версий API в одном и том же источнике Java - PullRequest
6 голосов
/ 07 апреля 2009

Я уверен, что это глупый вопрос, но .. У нас одни и те же исходные файлы Java, и мы хотим использовать другую версию Java API (jar-файл) в зависимости от клиента, для которого мы создаем наше приложение.

В более новой версии API есть методы setAAA () и setBBB (), на которые мы ссылаемся в нашем источнике Java:

if (...) {
  api.setAAA(a);
  api.setBBB(b);
} 

Этот код не будет работать, если скомпилированный со старым API имеет старый API, не имеющий этих установщиков. Есть ли способ обусловить этот код, чтобы компилировать строки установщика, только если мы используем новый API?

Спасибо.

Ответы [ 8 ]

5 голосов
/ 07 апреля 2009

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

Если это решение не подходит или нежелательно, я возвращаюсь к инъекция зависимости . Фреймворк Spring - безусловно, самый популярный и распространенный DI-фреймворк, но далеко не единственный. Guice - это еще один. Вы даже можете свернуть свои собственные, если нежелательно добавлять для этого полную структуру.

Но у меня есть проблемы с представлением Java-приложения, в частности Web / J2EE-приложения, которое я делаю без использования Spring. Это просто слишком полезно.

Допустим, есть 4 версии соответствующей банки. API менялся дважды за это время, поэтому у вас есть 3 разные версии API. Вам необходимо абстрагировать использование этого jar-файла от собственного API, который согласован для всех этих версий, а затем создать три его реализации: по одной для каждой отдельной версии API.

В Spring вы создаете контекст приложения, который определяет все ваши bean-компоненты и то, как они внедряются в другие bean-компоненты. Нет причины, по которой вы не можете выбирать или создавать контекст приложения как часть процесса сборки. Часто для этого используются свойства, но вы также можете включить часть контекста приложения таким же образом.

Ключевым моментом здесь является то, что, хотя API и отличаются, вам необходимо абстрагироваться от этих различий в том, что касается вашего кода. Если нет, то вы просто напрашиваетесь на неприятности, и это становится еще более грязным.

2 голосов
/ 07 апреля 2009

Java на самом деле не предназначалась для этой условной компиляции (в отличие от C ++), и она, честно говоря, звучит как рецепт для попадания в "classpath hell".

Хотя вы можете вручную начать работать с функциями, которые возвращают версию вашего API, у вас есть файл класса, который соответствует определенной версии, но без признаков того, что он может быть несовместим.

Я сталкивался с такой ситуацией раньше (например, работая с разными версиями Eclipse), и это не очень красиво. В итоге я получил интерфейс с двумя различными реализациями, по одной для каждого API, поместил каждый из них в отдельный проект (в моем случае плагин), а затем попытался загрузить их с помощью фабрики или инъекции. Изолируйте их как можно лучше.

1 голос
/ 07 апреля 2009

Вы можете скомпилировать с наименьшим общим знаменателем, а затем использовать отражение, чтобы вызвать метод, который доступен только в более поздних API. Например, предположим, что в классе com.foo.Bar метод "getFoogle ()" был заменен в более поздних версиях вашего API методом "getFiggle ()". И давайте предположим, что метод (в любом варианте) принимает int и double и возвращает int. Вы делаете вызов оболочки следующим образом:

public int getFoogleFiggle(Bar bar, int n, double d) {
  try {
    Class clz = Class.forName("com.foo.Bar");
    Method m = clz.getMethod("getFiggle", new Class[] {Integer.class, Double.class});
    return (Integer) m.invoke(bar, new Object[] {n, d});
  } catch (NoSuchMethodException nsme) {
    return getFoogle(n, d);
  } catch (various other spurious exceptions) {
    ... deal with in intesresting ways ...
  }
}

Обратите внимание, что во время компиляции компилятору все равно, существует ли класс coo.foo.Bar и / или метод getFiggle.

1 голос
/ 07 апреля 2009

То, что я сделал в прошлом: как можно более аккуратно напишите минимальный объем кода, который взаимодействует с зависящими от версии аспектами библиотеки. Есть версия этого кода для каждой версии библиотеки. Пусть все они реализуют один и тот же интерфейс. Основная часть вашего приложения должна попытаться (с Class.forName и, возможно, небольшим отражением для построения) динамически загрузить версию, подходящую для последней библиотеки. Если это не поможет, вернитесь к статически связанной версии для старой библиотеки.

При правильном использовании sourcepath и classpath вы можете запретить использованию вашего основного кода новую библиотеку.

1 голос
/ 07 апреля 2009

Вы также можете хранить отдельные ветки вашей системы контроля версий, которые содержат код, специфичный для клиента (т. Е. Для конкретной версии)

0 голосов
/ 07 апреля 2009

У меня возникла такая же потребность, поскольку у нас есть код, который должен запускаться на всех версиях Java из Java 1.2, но некоторый код должен использовать преимущества более новых API, если они доступны.

После различных перестановок, использующих отражение для получения объектов Метода и их динамического вызова, я в общем остановился на подходе стиля обертки (хотя в некоторых случаях лучше просто сохранить отраженный метод как статический и вызвать его лучше - это зависит).

Ниже приведен пример класса «System Utility», который предоставляет некоторые более новые API. В этом примере используется Singleton, но он может легко создать несколько объектов, если это требуется базовому API.

Есть два класса:

  • SysUtil
  • SysUtil_J5

Последний используется, если JVM во время выполнения - Java 5 или новее. В противном случае резервные методы, которые совместимы в контракте, используются из реализации по умолчанию в SysUtil, которая использует только Java 4 или более ранние API. Каждый класс компилируется с помощью компилятора конкретной версии, поэтому случайное использование API Java 5+ в классе Java 4 невозможно:

SysUtil (скомпилировано с компилятором Java 4)

<code>import java.io.*;
import java.util.*;

/**
 * Masks direct use of select system methods to allow transparent use of facilities only
 * available in Java 5+ JVM.
 *
 * Threading Design : [ ] Single Threaded  [x] Threadsafe  [ ] Immutable  [ ] Isolated
 */

public class SysUtil
extends Object
{

/** Package protected to allow subclass SysUtil_J5 to invoke it. */
SysUtil() {
    super();
    }

// *****************************************************************************
// INSTANCE METHODS - SUBCLASS OVERRIDE REQUIRED
// *****************************************************************************

/** Package protected to allow subclass SysUtil_J5 to override it. */
int availableProcessors() {
    return 1;
    }

/** Package protected to allow subclass SysUtil_J5 to override it. */
long milliTime() {
    return System.currentTimeMillis();
    }

/** Package protected to allow subclass SysUtil_J5 to override it. */
long nanoTime() {
    return (System.currentTimeMillis()*1000000L);
    }

// *****************************************************************************
// STATIC PROPERTIES
// *****************************************************************************

static private final SysUtil            INSTANCE;
static {
    SysUtil                             instance=null;

    try                  { instance=(SysUtil)Class.forName("SysUtil_J5").newInstance(); } // can't use new SysUtil_J5() - compiler reports "class file has wrong version 49.0, should be 47.0"
    catch(Throwable thr) { instance=new SysUtil();                                                                    }
    INSTANCE=instance;
    }

// *****************************************************************************
// STATIC METHODS
// *****************************************************************************

/**
 * Returns the number of processors available to the Java virtual machine.
 * <p>
 * This value may change during a particular invocation of the virtual machine. Applications that are sensitive to the
 * number of available processors should therefore occasionally poll this property and adjust their resource usage
 * appropriately.
 */
static public int getAvailableProcessors() {
    return INSTANCE.availableProcessors();
    }

/**
 * Returns the current time in milliseconds.
 * <p>
 * Note that while the unit of time of the return value is a millisecond, the granularity of the value depends on the
 * underlying operating system and may be larger. For example, many operating systems measure time in units of tens of
 * milliseconds.
 * <p>
 * See the description of the class Date for a discussion of slight discrepancies that may arise between "computer time"
 * and coordinated universal time (UTC).
 * <p>
 * @return         The difference, measured in milliseconds, between the current time and midnight, January 1, 1970 UTC.
 */
static public long getMilliTime() {
    return INSTANCE.milliTime();
    }

/**
 * Returns the current value of the most precise available system timer, in nanoseconds.
 * <p>
 * This method can only be used to measure elapsed time and is not related to any other notion of system or wall-clock
 * time. The value returned represents nanoseconds since some fixed but arbitrary time (perhaps in the future, so values
 * may be negative). This method provides nanosecond precision, but not necessarily nanosecond accuracy. No guarantees
 * are made about how frequently values change. Differences in successive calls that span greater than approximately 292
 * years (263 nanoseconds) will not accurately compute elapsed time due to numerical overflow.
 * <p>
 * For example, to measure how long some code takes to execute:
 * <p><pre>
 *    long startTime = SysUtil.getNanoTime();
 *    // ... the code being measured ...
 *    long estimatedTime = SysUtil.getNanoTime() - startTime;
 * 
*

* @return Текущее значение системного таймера в наносекундах. * / static public long getNanoTime () { return INSTANCE.nanoTime (); } } // КОНЕЦ ОБЩЕСТВЕННОГО КЛАССА

SysUtil_J5 (скомпилировано с компилятором Java 5)

import java.util.*;

class SysUtil_J5
extends SysUtil
{

private final Runtime                   runtime;

SysUtil_J5() {
    super();

    runtime=Runtime.getRuntime();
    }

// *****************************************************************************
// INSTANCE METHODS
// *****************************************************************************

int availableProcessors() {
    return runtime.availableProcessors();
    }

long milliTime() {
    return System.currentTimeMillis();
    }

long nanoTime() {
    return System.nanoTime();
    }

} // END PUBLIC CLASS
0 голосов
/ 07 апреля 2009

Вы можете попробовать

  • Вызов на основе отражения или генерация кода или старая техника предварительной обработки или

  • Шаблон стратегии для инкапсуляции изменений.

class ThirdPartyApi {
     void foo(){}  // available in all versions
     void bar(){}  // available only in new version
}

ThirdPartyApiV1 extends ThirdPartyApi {
     void foo() {
        thirdpartyV1Object.foo();
     }
}

ThirdPartyApiV2 extends ThirdPartyApi {
     void foo() {
        thirdpartyV2Object.foo();
     }
     void bar() {
        thirdpartyV2Object.bar();
     }
}

Используйте DependencyInjection, чтобы внедрить правильную версию реализации ThridPartyApi. В противном случае используйте ThirdPartyApiFactory для создания соответствующего экземпляра на основе значения конфигурации или системного свойства.

0 голосов
/ 07 апреля 2009

Вы можете использовать Java самоанализ. Посмотрите на пакет:

java.lang.reflect

У него есть класс с именем Method. Вы можете получить все открытые методы класса, используя:

Method[] methodList = obj.getClass().getMethods();

Поскольку это API, сеттеры будут открытыми. Затем вы можете запустить массив methodList и проверить те методы, которые имеют то же имя, что и сеттеры. Если вы найдете их, используйте их. В противном случае вы знаете, что это более ранняя версия.

Кроме того, большинство хорошо разработанных API имеют функцию, которая возвращает значение текущей версии файла JAR.

Например:

String currentVersion = api.SomeClass.version() ;

Попробуйте проверить, есть ли такая функция в используемом вами API. Это было бы проще.

...