Reflecting on classes from a Jar that isn’t in your classpath
Posted by: Matt in Software development, tags: java, Software developmentI was recently writing a program to find classes that had a certain annotation on them:
import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * This annotation works similarly to the@WebMethod* annotation. It is used to indicate that the annotated method is allowed to be * made visible on a generated facade, and how to do it. * */ @Target(ElementType.METHOD) @Documented @Retention(RetentionPolicy.RUNTIME) public @interface FacadeMethod { /** * This is the operation name that the annotated object will have on the * generated facade. This will be used as the value for *operationNamein the@WebMethod* annotation as well as the name of the method in the facade. * * @return the name that the annotated object will have on the generated * facade */ String operationName(); /** * This is a description of the method which will be used to generate * javadoc on the facade. * * @return a description of the method */ String description() default ""; }
Methods annotated with this method look something like this:
@FacadeMethod(operationName = "effectivelySame",
description = "Return true if the two strings are identical, both null, "
+ "or equivalent after converting to same case and trimming")
public static boolean effectivelySame(String s1, String s2) {
...
What I needed to do was look through a whole mess of jar files that weren’t in the classpath, looking for classes with methods annotated with this annotation, so I could read the operationName and the description properties of the annotation.
Ok, I thought, I need to use reflection, and the classes aren’t in the classpath. Sounds like I need my own ClassLoader. So I wrote a simple ClassLoader geared around adding .jar files by name to the classpath:
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
/**
* This class loads jars in new classloader
*/
public class JarFileLoader extends URLClassLoader
{
/**
* Constructor, required by superclass
*/
public JarFileLoader()
{
super(new URL[] {}, null);
}
/**
* Add a jar file to the classpath
*
* @param path
* the name of the jar file
* @throws MalformedURLException
* if the url built up doesn't work out
*/
public void addFile(String path) throws MalformedURLException
{
File f = new File(path);
URL urlPath = f.toURL();
addURL(urlPath);
}
}
which I can use like this to add jars, then load classes by name:
File d = new File(dir);
File[] listFiles = d.listFiles();
JarFileLoader jfl = new JarFileLoader();
for (File file : listFiles)
{
jfl.addFile(jarFileName);
}
Class clazz = jfl.loadClass("com.mattharrah.my.Javaclass");
Great — I had all the classes in all the jars of interest in the classpath of my new classloader - except as many of you know, there is no way through reflection to get a list or enumeration of the classes in a package, or the packages in the classpath. So how to examine all the classes in the Jars? Why, brute force of course! I start making a List of the names of all the classes I find by opening the Jar file and looking at the files inside, hanging on to all the files ending in “.class”:
String jarFileName = d.getCanonicalPath() + "/" + file.getName();
JarFile jf = new JarFile(jarFileName);
Enumeration entries = jf.entries();
while (entries.hasMoreElements())
{
JarEntry entry = entries.nextElement();
String n = entry.getName();
if (!n.endsWith(".class"))
{
continue;
}
String cn = n.substring(0, n.length() - 6).replaceAll("/", ".");
classes.add(cn);
}
jf.close();
Now with this list I can use my class loader to load all the classes by name, and then use reflection to find the methods on them, and for each one, get the annotations:
for (String cn : classes)
{
Class c = jfl.loadClass(cn);
Method[] methods = c.getDeclaredMethods();
for (Method m : methods)
{
Annotation a = m.getAnnotation(FacadeMethod.class);
}
}
Uh-oh! This doesnt work!!!! The FacadeMethod class in the system classloader is not the same FacadeMethod class as found in my special class loader — the Jar-based class loader returns a dynamic proxy object for the Annotation. To find the FacadeMethod annotation, it is possible to use method.getAnnotations(), which returns an array of all the annotations on the method, and check for its name:
Annotation[] declaredAnnotations = m.getDeclaredAnnotations();
for (Annotation annotation : declaredAnnotations) {
if (annotation.annotationType().getName().endsWith("FacadeMethod")) {
// This is the annotation I want, so work with it
But then we still face a problem: The annotation reference is not of the subclass that has the properties I need on it, but a basic Annotation — and there is apparently no way to cast it without getting a ClassCastException.
So how to get to the annotation’s custom properties? Again, brute force — parse the String representation (ick!).
If anyone reading this knows how to solve this last problem, I’d be fascinated to know how.

Entries (RSS)