Wednesday, 16 May 2012

How to create a custom ClassLoader for custom Annotations

Custom annotations have become an important part of most frameworks. So have custom classloaders. In this article, I am going to create a custom annotation, and a custom classloader that uses that annotation. I won't be doing anything fancy with the annotation, just printing out the fields in the class that make use of it. Before you start, you might want to read my article on Java Reflection API if you do not know how a class can infer information about another class.

My custom annotation is going to be @CustomAnnotations.AUTO. One has to make the annotation available at runtime, and that is done by using another annotation named RetentionPolicy.RUNTIME. Here is the class that defines my custom annotation.
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;

/**
 * Defines the custom annotation that will be used later on
 * Also holds a list of fields that made use of the custom annotation
 *
 * @author Hathy
 */
public class CustomAnnotations {

    @Retention(RetentionPolicy.RUNTIME)
    public @interface AUTO{
    }    

    public static ArrayList<String> autoFields=new ArrayList<String>();
                // This holds the names of the fields that made
                // use of my custom annotation
}
Now that the custom annotation is ready to be used, let me create a simple class that uses it.
/**
* A sample class that makes use of the custom annotations
*
* @author Hathy
*/
public class CustomAnnotationsUser {

    @CustomAnnotations.AUTO
    public String ID1;

    @CustomAnnotations.AUTO
    public String ID2;

    public static void main(String[] args) throws Exception{
        System.out.println(CustomAnnotations.autoFields);
    }
}
That was a very simple class that simply has two fields that make use of the annotation. Now comes the final part, where I define a custom ClassLoader that checks all classes for fields that make use of my custom annotation, and if yes, adds the names of those fields to autoFields. This class must extend the URLClassLoader class for it to be usable as a default system classloader. For simplicity, I check only if the first annotation is my custom annotation.
import java.lang.reflect.Field;
import java.net.URL;
import java.net.URLClassLoader;

/**
* A custom classloader that can be used as a system class loader.
*
* It checks if a field has the CustomAnnotations.AUTO and if yes,
* adds it to the autoFields arraylist.
*
* @author Hathy
*/
public class CustomAnnotationsLoader extends URLClassLoader {

    public CustomAnnotationsLoader(ClassLoader cl){
        super(new URL[0],cl);
    }

    @Override
    public Class<?> loadClass(String className)
                        throws ClassNotFoundException {
        Class c=super.loadClass(className);
        Field fields[]=c.getFields();
        for(Field field:fields){
            if(field.getAnnotations().length>0){
                String annotationName=field.getAnnotations()[0]
                                           .annotationType()
                                           .getCanonicalName();

                //The check happens here
                if(annotationName.indexOf("CustomAnnotations.AUTO")!=-1){
                    CustomAnnotations.autoFields.add(field.getName());
                }
            }
        }
        return c;
    }

}
That is all there is to it. I compile all of them as usual, using the standard javac compiler. However, when I run my CustomAnnotationsUser.class, I will use the following additional parameter while calling java
$ java -Djava.system.class.loader=CustomAnnotationsLoader CustomAnnotationsUser
When I run it, I get the following output, as expected,
[ID1, ID2]
So, what do you think about this article? Please do send in your reviews and comments. Thanks for reading.

No comments:

Post a Comment