Wrapper Classes for Backward compatibility in Java
There is an interesting article here on maintaing backwards compatibility for Java. In the wrapper class section, I can't actually understand what the wrapper class accomplishes. In the following code from MyApp
, WrapNewClass.checkAvailable()
could be replaced by Class.forName("NewClass")
.
static {
try {
WrapNewClass.checkAvailable();
mNewClassAvailable = true;
} catch (Throwable ex) {
mNewClassAvailable = false;
}
}
Consider when NewClass
is unavailable. In the code where we use the wrapper (see below), all we have done is replace a class that doesn't exist, with one that exists, but which can't be compiled as it uses a class that doesn't exist.
public void diddle() {
if (mNewClassAvailable) {
WrapNewClass.setGlobalDiv(4);
WrapNewClass wnc = new WrapNewClass(40);
System.out.println("newer API is available 开发者_如何学C- " + wnc.doStuff(10));
}else {
System.out.println("newer API not available");
}
}
Can anyone explain why this makes a difference? I assume it has something to do with how Java compiles code - which I don't know much about.
The point of this is to have code which is compiled against some class which may not be available at runtime. WrapNewClass has to be present in the classpath of javac, or this thing can't be compiled. However, it can be absent from the classpath at runtime.
The code you quote avoids references to WrapNewClass if mNewClassAvailable is false. Thus, it will just print the 'new API not available' message.
However, I can't say that I'm impressed. In general, I've seen this sort of thing arranged with java.lang.reflect instead of trying to catch the exception. That, in passing, allows the class to be nowhere in sight even when compiled.
I have long had the need to support every JVM since 1.1 in JSE and have used these kind of wrapping techniques to compatibly support optional APIs - that is, APIs which make the application work better, but are not essential to it.
The two techniques I use seem to be (poorly?) described in the article you referenced. Rather than comment further on that, I will instead provide real examples of how I have done this.
Easiest - Static Wrapper Method
Need: To invoke an API if it is available, or otherwise do nothing. This can be compiled against any JVM version.
First, set up a static Method
which has the reflected method, like so:
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;
}
and wrap the reflected method instead of using a direct call:
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
}
}
Harder - Static Wrapper Class
Need: To invoke an API if it is available, or otherwise invoke an older API for equivalent, but degraded, functionality. This must be compiled against the newer JVM version.
First set up a static wrapper class; this may be a static singleton wrapper, or you might need to wrap every instance creation. The example which follows uses a static singleton:
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
and create a subclass to provide the newer functionality when available:
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
I've seen this behaviour in spring and richfaces. Spring, for example, does the following
- has a compile-time dependency on JSF
- declares a
private static
inner class where it references the JSF classes - try/catches
Class.forName(..)
a JSF class - if no exception is thrown, the inner class is referenced (and the spring context is obtained through the faces context)
- 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
)
精彩评论