Monday, April 20, 2009

Introspection, the hard way

My current project is an Ext application with a database backend. The communication between the two is carried out by a rather thin JEE middleware, which serves JSON data to the client. The Java-to-JSON mapping is done by the JsonTools library, which makes this mapping a breeze:


String str = JSONMapper.toJSON(bean).render(true);


Really simple and effective. The library gets the bean properties by introspection, and converts them to their respective JSON representations, so there is no need to write any mapping XMLs or anything- just as you were writing in Ruby :)

However, today I stumbled on a nasty surprise. I created a new class hierarchy, with an abstract class on the top, with non-public concrete classes. The concrete classes are constructed using a Factory pattern, like in the example below:


public abstract class Animal {
public static Animal createAnimal() {
return new Elephant();
}
}

class Elephant extends Animal {
private String weight;
public String getWeight() {...}
public void setWeight(String weight) {...}
}


Everything nice and clean, however:


Animal dumbo = Animal.createAnimal();
System.out.println(JSONMapper.toJSON(dumbo).render(true));
//...


And KA-POW, an IllegalAccessException. What the?

The problematic snippet of code looks something like this:


Class dumboClass = dumbo.getClass();

System.out.println("dumbo is a "+dumboClass.getName()); //Elephant

PropertyDescriptor[] propDscs =
Introspector.getBeanInfo(dumboClass, Introspector.USE_ALL_BEANINFO).
getPropertyDescriptors();

for(PropertyDescriptor pd: propDscs) {
if(pd.getName().equals("class")) continue;

Method preader = pd.getReadMethod();
System.out.println("dumbo."+pd.getDisplayName()+" has a reader "+preader.getName());
//--> dumbo.weight has a reader getWeight -- ok
System.out.println("dumbo."+pd.getName() +"() = "+preader.invoke(dumbo));
// IllegalAccessException
}


If you think a bit about this, it makes perfect sense. Because the dumbo variable is declared as Animal instance, the class using it has no way to know about any of its methods other than the ones defined in the Animal class. Moreover, because the Elephant class is not public, it is not visible outside its package at all, as well as any of its methods.

However, what is surprising is that the Introspector somehow knows about both the Elephant class and its getWeight methods. What is even more surprising, is that you can write:


preader.setAccessible(true);


which makes the code work without any exceptions. The setAccessible method effectively nullifies any access restrictions- which is a mixed blessing, I imagine.

No comments: