【问题标题】:Wrapper Classes for Backward compatibility in JavaJava 中向后兼容的包装类
【发布时间】:2010-05-11 00:44:08
【问题描述】:

这里有一个interesting article,用于维护 Java 的向后兼容性。在包装类部分,我实际上无法理解包装类完成了什么。在MyApp 的以下代码中,WrapNewClass.checkAvailable() 可以替换为Class.forName("NewClass")

static {
    try {
        WrapNewClass.checkAvailable();
        mNewClassAvailable = true;
    } catch (Throwable ex) {
        mNewClassAvailable = false;
    }
}

考虑NewClass 不可用的情况。在我们使用包装器的代码中(见下文),我们所做的只是用一个存在的类替换一个不存在的类,但由于它使用了一个不存在的类而无法编译。

public void diddle() {
    if (mNewClassAvailable) {
        WrapNewClass.setGlobalDiv(4);
        WrapNewClass wnc = new WrapNewClass(40);
        System.out.println("newer API is available - " + wnc.doStuff(10));
    }else {
        System.out.println("newer API not available");
    }
}

谁能解释为什么这会有所不同?我认为这与 Java 编译代码的方式有关——我不太了解。

【问题讨论】:

  • 你链接到的那篇文章是一个非常糟糕的例子,这是一个策略模式的地方,就像你说的一个简单的调用 Class.forName() 来做检查。

标签: java backwards-compatibility


【解决方案1】:

这样做的目的是让代码编译 针对某些在运行时 可能不可用的类。 WrapNewClass 必须存在于 javac 的类路径中,否则这个东西无法编译。但是,它可能在 runtime 的类路径中不存在。

如果 mNewClassAvailable 为 false,您引用的代码将避免对 WrapNewClass 的引用。因此,它只会打印“新 API 不可用”消息。

但是,我不能说我印象深刻。一般来说,我已经看到了用 java.lang.reflect 安排的这种事情,而不是试图捕获异常。顺便说一句,即使在编译时,也无法看到该类。

【讨论】:

  • 如果mNewClassAvailable 为假,它会避免使用对WrapNewClass 的引用,但它是否仍然需要编译那段代码?
  • @Casebash 我会忘记这个成语并继续前进。
【解决方案2】:

自 1.1 以来,我一直需要在 JSE 中支持每一个 JVM,并使用这些包装技术来兼容地支持可选 API——也就是说,使应用程序更好地工作但对它不是必需的 API。

我使用的两种技术似乎(很差?)在您引用的文章中有所描述。我不会对此进行进一步评论,而是提供我如何做到这一点的真实示例。

最简单的 - 静态包装方法

需要:如果 API 可用,则调用它,否则什么也不做。这可以针对任何 JVM 版本进行编译。

首先,设置一个具有反射方法的静态Method,如下所示:

static private final java.lang.reflect.Method SET_ACCELERATION_PRIORITY;
static {
    java.lang.reflect.Method mth=null;
    try { mth=java.awt.Image.class.getMethod("setAccelerationPriority",new Class[]{Float.TYPE}); } catch(Throwable thr) { mth=null; }
    SET_ACCELERATION_PRIORITY=mth;
    }

并包装反射方法而不是使用直接调用:

static public void setImageAcceleration(Image img, int accpty) {
    if(accpty>0 && SET_ACCELERATION_PRIORITY!=null) {
        try { SET_ACCELERATION_PRIORITY.invoke(img,new Object[]{new Float(accpty)}); } 
        catch(Throwable thr) { throw new RuntimeException(thr); } // exception will never happen, but don't swallow - that's bad practice
        }
    }

更难 - 静态包装类

需要:调用可用的 API,或者调用旧的 API 以获得等效但降级的功能。这必须针对较新的 JVM 版本进行编译。

首先设置一个静态包装类;这可能是一个静态单例包装器,或者您可能需要包装每个实例创建。下面的例子使用了一个静态单例:

package xxx;

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

/** 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 milliTick() {
    return System.currentTimeMillis();
    }

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

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

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

    try                  { instance=(SysUtil)Class.forName("xxx.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 value of the most precise available system timer, in milliseconds.
 * <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 milliseconds since some fixed but arbitrary time (perhaps in the future, so
 * values may be negative). This method provides millisecond precision, but not necessarily millisecond accuracy. No
 * guarantees are made about how frequently values change. Differences in successive calls that span greater than
 * approximately 292,000 years 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.getNanoTick();
 *    // ... the code being measured ...
 *    long estimatedTime = SysUtil.getNanoTick() - startTime;
 * </pre>
 * <p>
 * @return          The current value of the system timer, in milliseconds.
 */
static public long getMilliTick() {
    return INSTANCE.milliTick();
    }

/**
 * 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 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.getNanoTick();
 *    // ... the code being measured ...
 *    long estimatedTime = SysUtil.getNanoTick() - startTime;
 * </pre>
 * <p>
 * @return          The current value of the system timer, in nanoseconds.
 */
static public long getNanoTick() {
    return INSTANCE.nanoTick();
    }

} // END PUBLIC CLASS

并创建一个子类以在可用时提供更新的功能:

package xxx;

import java.util.*;

class SysUtil_J5
extends SysUtil
{

private final Runtime                   runtime;

SysUtil_J5() {
    super();

    runtime=Runtime.getRuntime();
    }

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

long milliTick() {
    return (System.nanoTime()/1000000);
    }

long nanoTick() {
    return System.nanoTime();
    }
} // END PUBLIC CLASS

【讨论】:

    【解决方案3】:

    我在 spring 和 richfaces 中看到了这种行为。例如,Spring 会执行以下操作

    • 对 JSF 有编译时依赖
    • 声明一个 private static 内部类,它引用 JSF 类
    • 尝试/捕获 Class.forName(..) 一个 JSF 类
    • 如果没有抛出异常,则引用内部类(通过faces上下文获取spring上下文)
    • 如果抛出异常,则从另一个来源(servlet 上下文)获取 spring 上下文

    请注意,内部类在被引用之前不会加载,因此可以有一个在其中不满足的依赖项。

    (春季班是org.springframework.web.context.request.RequestContextHolder

    【讨论】:

    • 你能命名一个这样的 Spring 类,以便我查看源代码吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多