在同一Java源代码中处理不同的API版本

时间:2009-04-06 22:27:56

标签: java build-process configuration-management

我确定这是一个愚蠢的问题,但是..我们有相同的Java源文件,我们想要使用不同版本的Java API(jar文件),具体取决于我们构建应用程序的客户端。

较新版本的API具有我们在Java源代码中引用的方法setAAA()和setBBB():

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

如果使用旧API编译,旧代码API没有这些setter,则此代码将失败。如果我们使用新的API,有没有办法将此代码条件化为仅编译setter行?

感谢。

8 个答案:

答案 0 :(得分:5)

最安全的方法是回退到您需要支持的最低版本。这假设所有版本都向后兼容,但情况并非如此。

如果该解决方案不合适或不合适,那么我会回到dependency injectionSpring framework是目前最流行和最常见的DI框架,但并不是唯一的。 Guice是另一个。如果不希望为此添加完整的框架,您甚至可以自己动手。

但是我设想一个Java应用程序 - 特别是Web / J2EE应用程序 - 我没有使用Spring就会遇到问题。它太有用了。

假设有4个版本的相关jar。 API在此期间已更改两次,因此您有3种不同的API版本。您需要将该jar的使用抽象为您自己的API,这些API在所有这些版本中都是一致的,然后创建它的三个实现:每个不同的API版本一个。

在Spring中,您创建了一个应用程序上下文,它定义了所有bean以及它们如何注入其他bean。没有理由不能选择或构建应用程序上下文作为构建过程的一部分。通常使用属性,但您也可以通过这种方式包含部分应用程序上下文。

这里的关键点是,尽管API不同,但就代码而言,您需要抽象出这些差异。如果你不这样做,你只是在寻找麻烦而且它会变得更加混乱。

答案 1 :(得分:2)

Java真的不适合这种条件编译(不像C ++),老实说听起来像是一个以“类路径地狱”结束的秘诀。

虽然您可以手动开始处理返回API版本的函数,但是您有一个适合特定版本的类文件,但没有迹象表明它可能不兼容。

我之前遇到过这种情况(例如,使用不同版本的Eclipse)并且它并不漂亮。我最终做的是拥有一个具有两个不同实现的接口,每个API一个,将每个API放在一个单独的项目中(在我的情况下是一个插件),然后尝试用工厂或注入加载它们。尽可能隔离它们。

答案 2 :(得分:1)

您还可以保留版本控制系统的单独分支,其中包含客户特定的(即特定于版本的)代码

答案 3 :(得分:1)

我过去所做的是:尽可能干净地编写与库的版本相关方面交互的最少量代码。为每个版本的库提供此代码的版本。让它们都实现相同的界面。应用程序的大部分应该尝试(使用Class.forName并且可能使用 little 反射进行构建)动态加载适合最新库的版本。如果失败,请回退到旧库的静态链接版本。

通过适当使用sourcepath和classpath,您可以安排防止您的核心代码使用新库。

答案 4 :(得分:1)

您可以编译到最小公分母,然后使用反射来调用仅在以后的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 ...
  }
}

请注意,在 compile 时,编译器不关心类coo.foo.Bar和/或方法getFiggle是否存在。

答案 5 :(得分:0)

您可以使用java introspection。看看包装:

<强> java.lang.reflect中

它有一个名为Method的类。您可以使用以下方法获取类的所有公共方法:

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

由于它是一个API,因此setter将是公共的。然后,您可以运行数组methodList并检查那些与setter同名的方法。如果找到它们,请使用它们。否则,您知道这是早期版本。

此外,大多数开发良好的API都有一个函数,它返回当前版本的JAR文件的值。

例如:

String currentVersion = api.SomeClass.version() ;

尝试检查您使用的API中是否存在类似的功能。这会更容易。

答案 6 :(得分:0)

我有同样的需求,因为我们的代码需要在Java 1.2的所有Java版本上运行,但有些代码需要利用更新的API(如果可用的话)。

在使用反射获取方法对象并动态调用它们的各种排列之后,我总体上已经确定了最好的包装样式方法(尽管在某些情况下,只是将反射的方法存储为静态并调用它更好 - 它取决于。)

以下是一个示例“系统实用程序”类,它公开了某些较新的API。此示例使用Singleton,但如果底层API需要,则可以轻松实例化多个对象。

有两个类:

  • SysUtil
  • SysUtil_J5

如果运行时JVM是Java 5或更高版本,则使用后者。否则,在SysUtil中的默认实现中使用兼容兼容的回退方法,该实现仅使用Java 4或更早的API。每个类都使用特定版本的编译器进行编译,因此Java 4类中不会意外使用Java 5+ API:

SysUtil(使用Java 4编译器编译)

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;
 * </pre>
 * <p>
 * @return          The current value of the system timer, in nanoseconds.
 */
static public long getNanoTime() {
    return INSTANCE.nanoTime();
    }

} // END PUBLIC CLASS

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

答案 7 :(得分:-1)

您可以尝试

  • 基于反射的调用或代码生成或旧的预处理技术或

  • 策略模式以封装不同的内容。

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根据配置或系统属性值创建适当的实例。