Half the dynamic XML binding libraries use reflection to marshal Java object trees to XML and unmarshal XML back to objects. These libraries expect to work on JavaBean-compliant classes (or accompanying configuration files), and use reflection heavily bot ]]>
There are three main types of reflection operations:

  • Getting the Class object, using either Class.forName or some ClassLoader
  • Getting the Constructor, Method and Field objects from Class
  • Invoking newInstance on Constructor, invoke on Method or set* on Field

In most cases, the first two operation types return results that can be cached and reused, while the third type must be invoked for each specific operation (since the parameter values change). However, improper caching of the first two operation type results can lead to erroneous and unpredictable results when the underlying classes are changed during runtime.


One of the features of last-generation application servers is hot deploy, that allows swapping the class files without rebooting the whole application. In case of XML binders, this may mean either changes to existing get/set pairs implementation, or additional get/set pairs. Here is an example of such change, and the way it is handled in XStream:


First, we generate very simple test class (during run-time) with a single integer valueattribute:

 Logger _logger = Logger.getLogger(Main.class.getName()); // create first version of class String className = "TestClass"; String dir = "C:\\temp"; BufferedWriter bw1 = new BufferedWriter(new FileWriter(dir + File.separator + className + ".java")); bw1.write("public class " + className + " {"); bw1.newLine(); bw1.write(" private int value;"); bw1.newLine(); bw1.write(" public int getValue() {"); bw1.newLine(); bw1.write(" return this.value;"); bw1.newLine(); bw1.write(" }"); bw1.newLine(); bw1.write(" public void setValue(int value) {"); bw1.newLine(); bw1.write(" this.value = value;"); bw1.newLine(); bw1.write(" }"); bw1.newLine(); bw1.write("}"); bw1.newLine(); bw1.close(); 

Here is how the generated class looks like:

 public class TestClass { private int value; public int getValue() { return this.value; } public void setValue(int value) { this.value = value; } } 

Now, we compile it using tools.jar:

 // compile compStatus = com.sun.tools.javac.Main.compile(new String[] { dir + File.separator + className + ".java" }); _logger.info("Compilation status is " + compStatus); 

And load it using URLClassLoader:

 // load ClassLoader classLoader1 = new URLClassLoader(new URL[] { new File(dir) .toURL() }); Class clazz1 = classLoader1.loadClass(className); _logger.info("Loaded " + clazz1.getName() + " [" + clazz1.hashCode() + "]"); 

The resulting class is loaded, and its hash code is

 INFO [18:04:27.480] [XMain.main] Loaded TestClass [13577344] 

Now, we fetch its constructor and get/set pair:

 Constructor ctr1 = clazz1.getConstructor(new Class[0]); Method getter1 = clazz1.getMethod("getValue", new Class[0]); Method setter1 = clazz1 .getMethod("setValue", new Class[] { int.class }); 

Create new object, set valueto 10 and check that the set succeeded:

 Object obj1 = ctr1.newInstance(new Object[0]); setter1.invoke(obj1, new Object[] { new Integer(10) }); Object val1 = getter1.invoke(obj1, new Object[0]); _logger.info("Fetched value " + ((Integer) val1).intValue()); 

The result of get is, as expected, 10:

 INFO [18:04:27.480] [XMain.main] Fetched value 10 

Now, we create a new XStreamobject, configure it to handle our loaded class and marshal our object to XML:

 XStream xstream = new XStream(); xstream.alias("myobj", clazz1); String xml1 = xstream.toXML(obj1); _logger.info("First XML : " + xml1); 

The result is, as expected:

 INFO [18:04:27.652] [XMain.main] First XML : <myobj> <value>10</value> </myobj> 

Now, we unload the TestClass class (WeakReferenceis used to make sure that the class is unloaded):

 ReferenceQueue weakQueueCl = new ReferenceQueue(); WeakReference weakRefCl = new WeakReference(clazz1, weakQueueCl); weakRefCl.enqueue(); // Clear strong references clazz1 = null; // Invoke garbage collector - the reference will be queued System.gc(); try { Reference ref = weakQueueCl.remove(); ref.clear(); } catch (InterruptedException e) { e.printStackTrace(); return; } _logger.info("Reference to the class cleaned"); 

A few centiseconds later, the class is unloaded:

 INFO [18:04:27.699] [XMain.main] Reference to the class cleaned 

Now, we generate a new version of the same class, this time changing the getter implementation:

 BufferedWriter bw2 = new BufferedWriter(new FileWriter(dir + File.separator + className + ".java")); bw2.write("public class " + className + " {"); bw2.newLine(); bw2.write(" private int value;"); bw1.newLine(); bw2.write(" public int getValue() {"); bw2.newLine(); bw2.write(" return (this.value + 10);"); bw2.newLine(); bw2.write(" }"); bw2.newLine(); bw2.write(" public void setValue(int value) {"); bw2.newLine(); bw2.write(" this.value = value;"); bw2.newLine(); bw2.write(" }"); bw2.newLine(); bw2.write("}"); bw2.newLine(); bw2.close(); 

New version of our data class is

 public class TestClass { private int value; public int getValue() { return (this.value + 10); } public void setValue(int value) { this.value = value; } } 

Compile and load it, using a new URLClassLoader:

 // compile compStatus = com.sun.tools.javac.Main.compile(new String[] { dir + File.separator + className + ".java" }); _logger.info("Compilation status is " + compStatus); // load ClassLoader classLoader2 = new URLClassLoader(new URL[] { new File(dir) .toURL() }); // MyClassLoader classLoader2 = new MyClassLoader(Main.class // .getClassLoader(), dir); Class clazz2 = classLoader2.loadClass(className); _logger.info("Loaded " + clazz2.getName() + " [" + clazz2.hashCode() + "]"); 

The new class is loaded, and its hash code is different (as expected):

 INFO [18:04:28.011] [XMain.main] Loaded TestClass [26281671] 

Once again, we fetch the constructor and get/set pair:

 Constructor ctr2 = clazz2.getConstructor(new Class[0]); Method getter2 = clazz2.getMethod("getValue", new Class[0]); Method setter2 = clazz2 .getMethod("setValue", new Class[] { int.class }); 

And test that the changes to the getter function were loaded:

 Object obj2 = ctr2.newInstance(new Object[0]); setter2.invoke(obj2, new Object[] { new Integer(10) }); Object val2 = getter2.invoke(obj2, new Object[0]); _logger.info("Fetched value " + ((Integer) val2).intValue()); 

The result is, as expected, 20:

 INFO [18:04:28.011] [XMain.main] Fetched value 20 

Now, we create a new XStreamobject and ask it to marshal the new object:

 XStream xstream2 = new XStream(); xstream2.alias("myobj", clazz2); String xml2 = xstream2.toXML(obj2); _logger.info("Second XML : " + xml2); 

The result is erroneous:

 INFO [18:04:28.011] [XMain.main] Second XML : <myobj> <value>10</value> </myobj> 

In case we reuse the same XStreamobject, it throws exception:

 // xstream.alias("myobj", clazz2); String xml2 = xstream.toXML(obj2); _logger.info("Second XML : " + xml2); 

The exception is:

 Exception in thread "main" com.thoughtworks.xstream.converters.reflection.ObjectAccessException: Could not get field class java.lang.reflect.Field.value : null at com.thoughtworks.xstream.converters.reflection.PureJavaReflectionProvider. visitSerializableFields(PureJavaReflectionProvider.java:110) at com.thoughtworks.xstream.converters.reflection.ReflectionConverter. marshal(ReflectionConverter.java:44) at com.thoughtworks.xstream.core.ReferenceByXPathMarshaller. convertAnother(ReferenceByXPathMarshaller.java:36) at com.thoughtworks.xstream.core.TreeMarshaller.start(TreeMarshaller.java:46) at com.thoughtworks.xstream.core.ReferenceByXPathMarshallingStrategy. marshal(ReferenceByXPathMarshallingStrategy.java:17) at com.thoughtworks.xstream.XStream.marshal(XStream.java:461) at com.thoughtworks.xstream.XStream.marshal(XStream.java:451) at com.thoughtworks.xstream.XStream.toXML(XStream.java:432) at wr.XMain.main(XMain.java:134) Caused by: java.lang.IllegalArgumentException at sun.reflect.UnsafeFieldAccessorImpl.ensureObj(Unknown Source) at sun.reflect.UnsafeIntegerFieldAccessorImpl.getInt(Unknown Source) at sun.reflect.UnsafeIntegerFieldAccessorImpl.get(Unknown Source) at java.lang.reflect.Field.get(Unknown Source) at com.thoughtworks.xstream.converters.reflection.PureJavaReflectionProvider. visitSerializableFields(PureJavaReflectionProvider.java:108) ... 8 more 

This exception happens also when the alias call is uncommented.


Few techniques can be employed here, one of them being WeakHashMap, and another not using class names as keys in reflection caches. In any case, when you use reflection, you can not assume that the cached results will always be valid. In addition, libraries such as JiBX that inject marshaling and unmarshaling code to the data classes must be used not only during the build, but each time the classes are changed and compiled at runtime.