3

这里有一篇关于维护 Java 向后兼容性的有趣文章。在包装类部分,我实际上无法理解包装类完成了什么。在以下代码中MyAppWrapNewClass.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 编译代码的方式有关——我对此知之甚少。

4

3 回答 3

4

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

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

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

于 2010-05-11T00:53:16.187 回答
3

自 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
于 2010-05-11T02:32:14.460 回答
2

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

  • 对 JSF 有编译时依赖
  • 声明一个private static内部类,它在其中引用 JSF 类
  • 尝试/捕获Class.forName(..)一个 JSF 类
  • 如果不抛出异常,则引用内部类(通过faces上下文获取spring上下文)
  • if exception is thrown, the spring context is obtained from another source (the servlet context)

Note that inner classes are not loaded until they are referenced, so it is OK to have a dependency that is not met in it.

(The spring class is org.springframework.web.context.request.RequestContextHolder)

于 2010-05-11T05:27:53.277 回答