6

我确定这是一个愚蠢的问题,但是.. 我们有相同的 Java 源文件,我们希望根据我们构建应用程序的客户端使用不同版本的 Java API(jar 文件)。

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

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

如果使用旧 API 编译而旧 API 没有这些设置器,则此代码将失败。如果我们使用新的 API,是否有任何方法可以条件化此代码以仅编译 setter 行?

谢谢。

4

8 回答 8

5

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

如果该解决方案不合适或不理想,那么我将退回到依赖注入Spring 框架是迄今为止最流行和最常见的 DI 框架,但也不是唯一的。古斯是另一个。如果不希望为此添加一个完整的框架,您甚至可以自行开发。

但是我在设想一个不使用 Spring 的 Java 应用程序(尤其是 Web/J2EE 应用程序)时遇到了问题。它太有用了。

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

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

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

于 2009-04-06T22:37:47.277 回答
2

Java 真的不适合这种条件编译(与 C++ 不同),老实说,这听起来像是最终陷入“类路径地狱”的秘诀。

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

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

于 2009-04-06T22:39:15.010 回答
1

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

于 2009-04-06T22:38:07.277 回答
1

我过去所做的是:尽可能简洁地编写与库的版本相关方面交互的最少代码量。为每个版本的库提供此代码的版本。让它们都实现相同的接口。您的应用程序的大部分应该尝试(Class.forName可能还有一点构造反射)动态加载适合最新库的版本。如果失败,则回退到旧库的静态链接版本。

通过适当使用源路径和类路径,您可以安排防止您的核心代码使用新库。

于 2009-04-06T22:46:38.113 回答
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 ...
  }
}

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

于 2009-04-07T04:42:22.970 回答
0

您可以使用 java 内省。看包装:

java.lang.reflect

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

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

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

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

例如 :

String currentVersion = api.SomeClass.version() ;

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

于 2009-04-06T22:35:26.937 回答
0

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

在使用反射来获取 Method 对象并动态调用它们的各种排列之后,我通常选择最好的包装器样式方法(尽管在某些情况下,将反射的 Method 存储为静态并调用它会更好 - 这取决于) .

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

有两个类:

  • 系统实用程序
  • 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
于 2009-04-07T04:00:01.620 回答
-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 根据配置或系统属性值创建适当的实例。

于 2009-04-06T23:22:29.103 回答