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

在同一Java源代码中处理不同的API版本,java,build-process,configuration-management,Java,Build Process,Configuration Management,我肯定这是个愚蠢的问题,但是。。我们有相同的Java源文件,我们希望使用不同版本的Java API(jar文件),具体取决于我们构建应用程序的客户机 较新版本的API具有我们在Java源代码中引用的方法setAAA()和setBB(): if (...) { api.setAAA(a); api.setBBB(b); } 如果使用旧API编译的代码中包含这些设置器,而旧API中没有这些设置器,则此代码将失败。如果我们正在使用新的API,有没有办法将此代码条件化为只编译setter行

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

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

if (...) {
  api.setAAA(a);
  api.setBBB(b);
} 
如果使用旧API编译的代码中包含这些设置器,而旧API中没有这些设置器,则此代码将失败。如果我们正在使用新的API,有没有办法将此代码条件化为只编译setter行


谢谢。

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

java.lang.reflect

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

Method[] methodList = obj.getClass().getMethods();
因为它是一个API,所以setter是公共的。然后,您可以运行数组methodList并检查那些与setter同名的方法。如果你找到了,就使用它们。否则,您知道这是早期版本

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

例如:

String currentVersion = api.SomeClass.version() ;

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

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

如果这个解决方案不合适或不可取,那么我就求助于。到目前为止,是最流行和最常见的DI框架,但决不是唯一的一个。这是另一个。如果不希望为此添加完整的框架,您甚至可以自己滚动

但是我在设想一个Java应用程序时遇到了一些问题——特别是一个Web/J2EE应用程序——我没有使用Spring。它实在太有用了

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

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


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

你也可以保留版本控制系统的不同分支,这些分支保存特定于客户(即特定于版本)的代码

Java真的不适合这种条件编译(与C++不同),而且它听起来真的像是一种结束于“类路径地狱”

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


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

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

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

您可以尝试

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

  • 策略模式来封装变化的内容

使用DependencyInjection注入ThridPartyApi实现的正确版本。
否则,请使用第三方API工厂根据配置或系统属性值创建适当的实例。

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

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

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

有两类:

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

SysUtil(使用Java4编译器编译)
您可以编译到最低公分母,然后使用反射来调用仅在以后的API上可用的方法
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();
     }
}

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
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
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 ...
  }
}