Over a million developers have joined DZone.
Gold Partner

Discovering Java Annotations

· Java Zone
Annotations are proving to be widely popular these days. Many annotation-based frameworks (Spring, JPA, Tapestry to name few) are seeing the light of the day, and even small scale projects are using annotation based meta-programming for better separation of concerns. Annotations are wonderful and are sure to stay here.

However, in order to use this, we must be able to locate classes annotated with the Annotations that concern us. So, how can you do it?

First, you find resources where we can look for annotated classes. You can look into “java.class.path” system property (also known as CLASSPATH), or use Classloader or ServletContexts to get a list of Resources. Now, you scan through each resource and check for the annotations that concern you.

The easiest way to scan through a resource is to load it through a Classloader and use the Java Reflection API to look for the specified annotation. However, this approach will only help you to find annotations that are visible at runtime, and loading each resource into memory will consume an unnecessary amount of memory. Otherwise, you can use the ASM or Javassist byte processing libraries. These libraries process byte code and can see runtime annotations. And since they don’t load your resources into memory, they have a low footprint.

Well, to help you here, we have written a small library called Annovention using Javassist. The idea behind Annovention is to help you quickly locate annotated classes, fields or methods. Once you have the pertinent classes you can run your domain logic or do whatever you need to do.

Download Annovention here: http://code.google.com/p/annovention/

Read the detailed blog here:  http://anismiles.wordpress.com/2010/07/26/discovering-java-annotations/

How it works?

Annovention works on a subscribe-and-listen pattern.

  1. You create annotation discovery listeners.
    1. Annovetion supports Class, Field and Method level annotation discovery and each of these listeners must implement ClassAnnotationDiscoveryListener, FieldAnnotationDiscoveryListener or MethodAnnotationDiscoveryListener interfaces respectively.
    2. Each listener has a method supportedAnnotations() that returns an Array of Annotation names. For example, to listen to @Entity and @EntityListeners annotations, the array will be:
      new String[] {Entity.class.getName(), EntityListeners.class.getName()}
    3. Each listener has a method discovered() that is called each time a relevant annotation is located with Class-name, Field-name, Method-name and discovered Annotation-name.
    4. Please note that your listeners are receiving only names of classes, fields and methods. You must use Java Reflection to instantiate them. Remember Class.forName()?
  2. Now, there is a Discoverer class that performs all of the discoveries. It needs Resources and Listeners. Annovention comes with an implementation of ClasspathDiscoverer. This uses “java.class.path” system property and builds an array of resources to scan. You need to register your Listeners to the Discoverer class in order to get notified.

Sample Use

public class SampleAnnotationDiscoverer {

public static void main (String args []) {
// Get a classpath discoverer instance
Discoverer discoverer = new ClasspathDiscoverer();

// Register class annotation listener
discoverer.addAnnotationListener(new MyClassAnnotationListener());
// Register field annotation listener
discoverer.addAnnotationListener(new MyFieldAnnotationListener());
// Register method annotation listener
discoverer.addAnnotationListener(new MyMethodAnnotationListener());

// Fire it
discoverer.discover();
}

/** Dummy ClassAnnotation listener */
static class MyClassAnnotationListener implements ClassAnnotationDiscoveryListener {
private static Log log =
LogFactory.getLog(MyClassAnnotationListener.class);

@Override
public void discovered(String clazz, String annotation) {
log.info("Discovered Class(" + clazz + ") " +
"with Annotation(" + annotation + ")");
}

@Override
public String[] supportedAnnotations() {
// Listens for @Entity and @EntityListeners annotations.
return new String[] {
Entity.class.getName(),
EntityListeners.class.getName()};
}
}

/** Dummy FieldAnnotation listener */
static class MyFieldAnnotationListener implements FieldAnnotationDiscoveryListener {
private static Log log =
LogFactory.getLog(MyFieldAnnotationListener.class);

@Override
public void discovered(String clazz, String field, String annotation) {
log.info("Discovered Field(" + clazz + "." + field + ") " +
"with Annotation(" + annotation + ")");
}

@Override
public String[] supportedAnnotations() {
// Listens for @Id and @Column annotations.
return new String[] {
Id.class.getName(),
Column.class.getName()};
}
}

/** Dummy FieldAnnotation listener */
static class MyMethodAnnotationListener implements MethodAnnotationDiscoveryListener {

private static Log log =
LogFactory.getLog(MyMethodAnnotationListener.class);

@Override
public void discovered(String clazz, String method, String annotation) {
log.info("Discovered Method(" + clazz + "." + method + ") " +
"with Annotation(" + annotation + ")");
}

@Override
public String[] supportedAnnotations() {
// Listens for @PrePersist, @PreRemove and @PostPersist annotations.
return new String[] {
PrePersist.class.getName(),
PostPersist.class.getName(),
PreRemove.class.getName()};
}
}
}

How to extend?

You just have to play with the Discoverer and Filter classes. The general flow is:

  1. Discoverer invokes the findResources() method which returns an array of URLs and then
  2. It iterates through each URL and apply filter, and
  3. Then scan the resource for annotations and
  4. If a valid annotation is found, corresponding listeners are intimated.

You can write your own Filter implementation or use the FilterImpl class that comes with Annovention. Then, just extend the Discoverer class and implement the findResources() method in whatever way you see fit. Then just invoke the discover() method. Easy?

Topics:

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}