Java Reflection - A tool every java developer should know...
I have been working with java for more than three years now and this post about reflection is not about teaching how to use reflection API and it is not about sneaking you through the important classes or methods that form the reflection API rather I thought of presenting you a scenario from my development experience which I came across as part of architechting a solution for a business case and how reflection came to the rescue to accomplish my task. The scenario which I am going to present below made me realize the power of reflection and probably, that is why I think that java reflection is a very handy tool which every java developer should have it in their bag.
The problem statement goes like this:
I had a web application deployed on Tomcat and I had to make sure that one of my servlet running inside Tomcat registers itself(assume some kind entry should be made in one of the tables in the database being used) about its existence and also what (http) port its listening on. The servlet specification provides us with that knid of info as part of servicing an incoming HttpServletRequest (i.e. getServerPort()) but my problem is that I need the port number information during the servlet's init method (which, obviously, doesn't have an incoming HttpServletRequest).
During the initialization of my servlet, all I have is the ServletConfig object provided by the container as a parameter to the init method.I know, according to the servlet specs, I can get the ServletContext reference using getServletContext() on the config reference.I thought the context reference should help me with my solution, but going thorugh the servlet specs I could not find a direct way to get to the listening port number.This made me think to have a thorough examination of the ServletContext internals just to check if there is a way to fetch the port number which my servlet is listening on. I captured the context reference and had the eclipse run in debug mode, with a break point set on the context reference, I simply restarted my tomcat and there you go, eclipse is ready blinking with a highlighted green line on the breakpoint on the context reference. After a thourough inspection on the context reference using the debugger, I figured out "Surely, somewhere in here, there is some Catalina-specific classes that have a reference to the Tomcat configuration." Sure enough, after poking around for maybe an hour I finally found a chain of references that finally led me to some Catalina classes that held a reference to an internal integer that held a reference to port 8080.
Great! I now know that the ServletContext that I got inside the init method has a reference, albeit an indirect reference, to the port number. If that reference were depicted as a series of variable names using dot-notation, it would look like this: context.context.parent.parent.service.connectors[0].port
Okay, so let's walk through this. I have code with a reference to a ServletContext object. Through the debugger, I discovered that it has an undocumented internal variable named "context" and that thing, whatever it is, has an internal variable also named "context", and that thing has a variable named "parent", which, in turn, has its own "parent", which has a "service", which had an array of "Connector" objects stored in an array named "connectors", and each of these Connector objects had a variable named "port", and the first one (at element 0) was pointing to my HTTP port 8080. Now, all these variables are undocumented references which I cannot simply put to my use. So I thought, how does a bunch of undocumented, probably private, internal variables help you get a reference to the port at runtime?" That's where reflection comes in.
Reflection lets us invoke methods or access fields (internal class-level variables) dynamically, through the use of a generic API. The cool part is that it even works for private methods and fields.I started writing the reflection code right there where I need it, but my lead suggested me to come up with a utility class with all the reflection code that we can use to handle the problem statement.As they say, we should always look for utility classes as a solution to all common the problems that we will face time and again. A utility class - probably a static method, pass it my ServletContext object and some kind of dot-delimited field specification that will recursively follow the fields and return back whatever is held onto by the last one. This is what I really want to do:
ServletContext ctx = getServletContext();
Object[] connectors = (Object[]) ObjectUtil.getFieldValue(ctx, "context.context.parent.parent.service.connectors"); Object firstConnector = connectors[0];
int port = Integer.parseInt(ObjectUtil.getFieldValue(firstConnector, "port").toString());
Finally, here comes the sweet part, the utility methods with reflection code:
The code below depicts the code with the getFieldValue() method.
public static Object getFieldValue(Object owner, String fieldSpec)
{
String firstPart = fieldSpec;
String rest = null;
int index = fieldSpec.indexOf(".");
if(index != -1)
{
firstPart = fieldSpec.substring(0,index);
rest = fieldSpec.substring(index + 1);
}
Field f = getField(owner.getClass(), firstPart);
f.setAccessible(true); // this lets us access the field
Object value = null;
try
{
value = f.get(owner);
}
catch(IllegalArgumentException e)
{
throw new RuntimeException();
}
catch(IllegalAccessException e)
{
throw new RuntimeException();
}
if(rest != null)
{
return getFieldValue(value,rest);
}
else
{
return value;
}
}
The code below depicts the code with the getField() method.
private static Field getField(Class c, String name)
{
try
{
return c.getDeclaredField(name);
}
catch(SecurityException e)
{
throw new RuntimeException(e);
}
catch(NoSuchFieldException e)
{
if(c.getSuperclass() != null)
return getField(c.getSuperclass(), name);
else
throw new RuntimeException();
}
}
The code is very simple and speaks for itself.Just make a note, how the least of reflection API with the getClass(),getDeclaredFieldValue(), getSuperClass() and field.get() method are put to good use to solve a fairly complex problem.
|