animesh kumar

Running water never grows stale. Keep flowing!

Discovering Java Annotations

with 15 comments

DZone link of this artilce: http://www.dzone.com/links/r/discovering_java_annotations.html

Annotations these days are proving to be widely popular. 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 concern. Annotations are wonderful and it’s sure to stay here.

However, in order to use this, we must be able to locate classes annotated with Annotations that concerns us. How do 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 have a list of Resources. Now, you scan through each resource and check for Annotations that concern you.

The easiest way to scan through a resource is to load it through a Classloader and use Java Reflection Api to look for a specified annotation. However, this approach will only help you to find Annotations that are visible at runtime, and loading each resource into memory will unnecessarily consume Java Memory. Otherwise, you can use ASM or Javassist byte processing libraries. These libraries process class bytes and see even 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 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 fancies you.

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

How it works?

Annovention works on 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 does all 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 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 Discoverer and Filter classes. The general flow is:

  1. Discoverer invokes 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 FilterImpl class that comes with Annovention. Then, extend Discoverer class and implement findResources() method in whatever way you see fit. Then just invoke discover() method. Easy?

About these ads

Written by Animesh

July 26, 2010 at 6:08 pm

Posted in Technology

Tagged with , ,

15 Responses

Subscribe to comments with RSS.

  1. My experience with the spring support for auto discovery is that filtering by classpath resource is a must have. In your typical webapp you have 50+ jar files, but you are just interested in exploring WEB-INF/classes (or even more, com/acme/model/** classes).

    Experimenting with different servers is also important, as classpath entries can get quite tricky (proprietary protocols for accessing libraries inside WAR deployments for websphere and jboss, for instance).

    This is just a heads-up. The API looks really promising, and some independent product like this is certainly needed. Good job!

    Ignacio Coloma

    July 28, 2010 at 2:26 pm

  2. hi,if i have a class which has tow method whichi their name is same.and one is has the annotation,but other is not .how can i know which method i has the annotation

    wangsheng

    June 11, 2011 at 7:13 pm

  3. Hi Animesh… I am working on a project in java. Which involves writing a contract for a method using COFOJA. And then using heuristics on contract to generate code for the method.

    for ex: consider following input to my project

    class Test{

    @requires( { a> 0})
    @ensures( {a==0 implies fact(a)=1 , and a>0 implies fact(a) = fact(a-1)*a } )

    public int fact (int a)
    {

    }

    }

    For above contract, I am planning to generate AST, so that I can parse the contract and then applying heuristics , I can generate first version of code for method fact.

    Output of first version of code: (Its a rough estimate of code,)

    class Test1{

    public int fact (int a)
    {
    if (a==0)
    return 1;

    if(a >0)
    return a*fact(a-1);

    if(a<0) throw new AssertionException("Precondition failed/violated a<0 ");

    }

    } // end of class

    So, the main thing here is generation of AST. and whether I will be able to parse the contract or not? then only I can apply heuristics to it.

    Thanks

    Sagar Shinde

    July 20, 2011 at 5:05 am

    • Hi Sagar,

      Thanks for the comment. Is there something I can help you with?

      -Animesh

      Animesh

      July 20, 2011 at 9:53 am

      • Thanks for reply. I want to ask ,

        1) Do you have any idea, about how to scan the contracts in java? Since, I am using COFOJA for writing contracts, it uses @ensures, @requires etc.. annotations for precondition, postcondtions . SO, using your Annovention, will it be possible to scan these annotations, which are written using cofoja?

        2) Or is there any other way you know, by which I can parse the java code? I found AST as of the way, but not getting enough information on how to write code for Abstract Syntax tree.

        Kindly help.

        Thanks

        -sagar

        Sagar Shinde

        July 20, 2011 at 4:43 pm

      • Sagar, sure you can use Annovention to scan these annotations. Annovention can scan any Java Annotation. All you need to do is to write ClassAnnotationDiscoveryListener or FieldAnnotationDiscoveryListener or MethodAnnotationDiscoveryListener based upon where you are expecting the annotations. And then, write supportedAnnotations method to return the array of Annotation names you want to scan, like:

        public String[] supportedAnnotations() {
        return new String[] {
        com.google.java.contract.Ensures.class.getName(),
        com.google.java.contract.Requires.class.getName()
        }

        simple! Please follow the blog, it describes all the necessary steps.

        -Animesh

        Animesh

        July 20, 2011 at 9:47 pm

  4. Animesh,
    Nice work on the tool, source is understandable, it’s well written, and the API is easy to get. I’m using it with http://code.google.com/p/mash/ and it’s working great. Thanks for the effort.

    -troy

    troy

    December 6, 2011 at 11:39 pm

    • Wonderful. Good to know that it could be of some help. :) Mash looks promising too. Will explore it this weekend.

      Animesh

      December 12, 2011 at 10:59 pm

  5. Hi Animesh —

    Thanks for contributing a well-written, succinct and useful project. I was able to use it easily in one of my projects where I needed to scan for annotations and did not want to bring Spring into the picture.

    I had a quick question (I know I can fork this project and change the behavior, but wanted to check first). Why does the Discover class call the listeners with the string names for class, method, annotation etc. instead of passing them as objects. Most annotations have member values that need to be processed and so just getting the string value with the annotation name is not helpful in that regard. Also, I know that while the listener can look up the annotation again by using reflection, it seems unnecessary because the Discover had that those objects before calling the listeners.

    If you agree with my comments, would you be open to providing another method on the listener that the Discover can call that passes in the actual objects (mainly the Annotation object). Was there any reason you chose to not go that route?

    Thanks,
    Sang

    Sang

    March 30, 2012 at 11:45 pm

    • Discoverer uses Javassist – to find out annotations – which works on byte-code, not reflection. Objects Discoverer deals with are javassist.bytecode.* ones, so it would need to create Class, Method, Field and other object if it has to pass these to listener.
      I chose not to do this, and let whoever is listening deal with it. :) But, yes, one can very easily implement such a method.

      – Animesh

      Animesh

      March 31, 2012 at 7:37 pm

  6. Nice job. I am planning on using Annovation to build a resource index for a JAX-RS service. Can the AnnotationDiscoveryListener sub-classes access the full javassist.bytecode.annotation.Annotation object so that I can access and report the annotations parameters? I have figured out a way to do this by modifying Annovation but the AnnotationDiscoveryListener.discovered method would have to change. I am wondering if I can do it without modifying the package?

    Also, I am seeing dupes in the logs with my tester. ( http://pastebin.com/raw.php?i=a8tgNUFC ) any ideas?

    dbennett455

    February 13, 2014 at 2:48 am

    • I ended up modifying Annovention adding three new listeners in additional to the existing ones (xxxAnnotationObjectDiscoveryListener). I modified Discoverer to handle the additional listeners along with the original three. This way, the package maintains compatibility while adding some new capabilities. I added a test that reports on an annotations parameter value. I can send you the changes for inclusion if your interested. Otherwise, I’ll probably branch the project as it’s well suited to build the JAX-RS service resource hierarchy.

      dbennett455

      February 13, 2014 at 10:32 am

      • Please do send me a pull request.

        Animesh

        February 25, 2014 at 2:06 am


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 205 other followers

%d bloggers like this: