简体   繁体   English

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

[英]Handling different API versions in same Java source

I'm sure this is a dumb question, but.. We have the same Java source files and we want to use a different version of a Java API (jar file) depending on the client we are building our app for. 我确定这是一个愚蠢的问题,但是..我们有相同的Java源文件,我们想要使用不同版本的Java API(jar文件),具体取决于我们构建应用程序的客户端。

The newer version of the API has the methods setAAA() and setBBB() which we reference in our Java source: 较新版本的API具有我们在Java源代码中引用的方法setAAA()和setBBB():

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

This code will fail if compiled with the old API has the old API doesn't have these setters. 如果使用旧API编译,旧代码API没有这些setter,则此代码将失败。 Is there any way to conditionalize this code to only compile the setter lines if we are using the new API? 如果我们使用新的API,有没有办法条件化此代码只编译setter行?

Thanks. 谢谢。

The safest approach is to fall back to the lowest version you need to support. 最安全的方法是回退到您需要支持的最低版本。 That assumes all versions are backwards compatible which isn't necessarily the case. 这假设所有版本都向后兼容,但不一定如此。

If that solution isn't appropriate or desirable then I fall back to dependency injection . 如果该解决方案不合适或不合适,那么我将回归依赖注入 The Spring framework is by far the most popular and common DI framework but no means the only one. Spring框架是迄今为止最流行和最常见的DI框架,但并不是唯一的框架。 Guice is another one. Guice是另一个。 You can even roll your own if it's undesirable to add a complete framework for this. 如果不希望为此添加完整的框架,您甚至可以自己动手。

But I have problems envisioning a Java application--particularly a Web/J2EE application--that I do without using Spring. 但我设想一个Java应用程序 - 尤其是Web / J2EE应用程序 - 我没有使用Spring就会遇到问题。 It's simply too useful. 这太有用了。

Let's say there are 4 versions of a relevant jar. 假设有4个版本的相关jar。 The API has changed twice in that time so you have 3 different API versions. API在此期间已更改两次,因此您有3种不同的API版本。 You need to abstract use of that jar to an API of your own that is consistent across all these versions and then create three implementations of it: one for each different API version. 您需要将该jar的使用抽象为您自己的API,这些API在所有这些版本中都是一致的,然后创建它的三个实现:每个不同的API版本一个。

In Spring you create an application context, which defines all your beans and how they're injected into other beans. 在Spring中,您创建了一个应用程序上下文,它定义了所有bean以及它们如何注入其他bean。 There is no reason you can't choose or build and application context as part of a build process. 没有理由不能选择或构建应用程序上下文作为构建过程的一部分。 Often properties are used for this but you could also include part of the application context this way too. 通常使用属性,但您也可以通过这种方式包含部分应用程序上下文。

The key point here is that even though the APIs are different you need to abstract away those differences as far as your code is concerned. 这里的关键点是,尽管API不同,但就代码而言,您需要抽象出这些差异。 If you don't you're just asking for trouble and it just gets messier. 如果你不这样做,你只是在寻找麻烦而且它变得更加混乱。

Java really wasn't meant for this conditional compilation (unlike C++), and it honestly sounds like a recipe for ending up in "classpath hell". Java真的不适合这种条件编译(与C ++不同),它老实说听起来像是一个结束“classpath hell”的秘诀。

While you could manually start dealing with functions that return the version of your API, you then have a classfile that fits a specific version, but with no indications that it may be incompatible. 虽然您可以手动开始处理返回API版本的函数,但是您有一个适合特定版本的类文件,但没有迹象表明它可能不兼容。

I've encountered this situation before (eg, working with different versions of Eclipse) and it's not pretty. 我之前遇到过这种情况(例如,使用不同版本的Eclipse)并且它并不漂亮。 What I ended up doing is having an interface with two different implementations, one for each API, put each of them in a separate project (a plug-in in my case), and then tried to load them with a factory or an injection. 我最终做的是拥有一个具有两个不同实现的接口,每个API一个,将每个API放在一个单独的项目中(在我的情况下是一个插件),然后尝试用工厂或注入加载它们。 Isolate them as well as you can. 尽可能隔离它们。

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

What I've done in the past is: As cleanly as possible write the minimum amount of code that interacts with version dependent aspects of the library. 我过去所做的是:尽可能干净地编写与库的版本相关方面交互的最少量代码。 Have a version of this code for each version of the library. 为每个版本的库提供此代码的版本。 Have them all implement the same interface. 让它们都实现相同的界面。 The bulk of your application should try to (with Class.forName and possibly a little reflection for construction) dynamically load the version suitable for the latest library. 您的应用程序的大部分应该尝试(使用Class.forName并可能对构造进行一些反思)动态加载适合最新库的版本。 If that fails, fall back to a statically linked version for the old library. 如果失败,请回退到旧库的静态链接版本。

By appropriate use of sourcepath and classpath, you can arrange for your core code to be prevented from using the new library. 通过适当使用sourcepath和classpath,您可以安排防止核心代码使用新库。

You can compile to the lowest common denominator, then use reflection to call the method that is only available on later APIs. 您可以编译到最小公分母,然后使用反射来调用仅在以后的API上可用的方法。 For example, supposing that on the class com.foo.Bar, the method "getFoogle()" was superseded in later versions of your API by the method "getFiggle()". 例如,假设在类com.foo.Bar上,方法“getFoogle()”在API的更高版本中被“getFiggle()”方法取代。 And let's suppose the method (in either variant) takes an int and a double and returns an int. 让我们假设方法(在任一变体中)采用int和double并返回int。 You make a wrapper call as follows: 您按如下方式进行包装调用:

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

Note that at compile time, the compiler doesn't care whether or not the class coo.foo.Bar and/or the method getFiggle exist. 请注意,在编译时,编译器不关心类coo.foo.Bar和/或方法getFiggle是否存在。

You can use java introspection. 你可以使用java内省。 Look at the package : 看看包装:

java.lang.reflect java.lang.reflect中

It has a class called Method. 它有一个名为Method的类。 You can get all the public methods of a Class using : 您可以使用以下方法获取类的所有公共方法:

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

Since it is an API, the setters would be public. 由于它是一个API,因此setter将是公共的。 Then you can run through the array methodList and check for those methods which have the same name as the setters. 然后,您可以运行数组methodList并检查那些与setter同名的方法。 If you find them, use them. 如果找到它们,请使用它们。 Otherwise, you know this is an earlier version. 否则,您知道这是早期版本。

Also, most of the well developed API's have a function which returns the value of the current version of the JAR file. 此外,大多数开发良好的API都有一个函数,它返回当前版本的JAR文件的值。

For example : 例如 :

String currentVersion = api.SomeClass.version() ;

Try to check if there is a function like that in the API you are using. 尝试检查您使用的API中是否存在类似的功能。 This would be easier. 这会更容易。

I have had this same need, since we have code that needs to run on all versions of Java from Java 1.2, but some code needs to take advantage of newer API's if they are available. 我有同样的需求,因为我们的代码需要在Java 1.2的所有Java版本上运行,但有些代码需要利用更新的API(如果可用的话)。

After various permutations using reflection to obtain Method objects and invoking them dynamically, I have settled on a wrapper style approach as best, in general (although under some circumstances, just storing the reflected Method as a static and invoking it is better - it depends). 在使用反射获取方法对象并动态调用它们的各种排列之后,我已经确定了最好的包装样式方法(尽管在某些情况下,只是将反射的方法存储为静态并调用它更好 - 取决于它) 。

Following is an example "System Utility" class which exposes certain newer API's. 以下是一个示例“系统实用程序”类,它公开了某些较新的API。 This example uses a Singleton, but could easily instantiate multiple objects if the underlying API needed that. 此示例使用Singleton,但如果底层API需要,则可以轻松实例化多个对象。

There are two classes: 有两个类:

  • SysUtil SysUtil
  • SysUtil_J5 SysUtil_J5

The latter is the one used if the run-time JVM is Java 5 or later. 如果运行时JVM是Java 5或更高版本,则使用后者。 Otherwise fallback methods which are compatible in contract are used from the default implementation in SysUtil which utilizes only Java 4 or earlier APIs. 否则,在SysUtil中的默认实现中使用兼容兼容的回退方法,该实现仅使用Java 4或更早的API。 Each class is compiled with the specific version's compiler, so that there is no accidental usage of a Java 5+ API in the Java 4 class: 每个类都使用特定版本的编译器进行编译,因此Java 4类中不会意外使用Java 5+ API:

SysUtil (compiled with the Java 4 compiler) 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 (compiled with the Java 5 compiler) 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

You may try 你可以试试

  • Reflection based invocation or Code generation or the old preprocessing technique or 基于反射的调用或代码生成或旧的预处理技术或

  • Strategy pattern to encapsulate what varies. 策略模式来封装不同的东西。

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();
     }
}

Use a DependencyInjection to inject the correct version of ThridPartyApi implementation. 使用DependencyInjection注入正确版本的ThridPartyApi实现。 Otherwise use a ThirdPartyApiFactory to create the appropriate instance based on a configuration or system property value. 否则,使用ThirdPartyApiFactory根据配置或系统属性值创建适当的实例。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM