Alternative ways of toString() generation with AnnoJ

October 15, 2009

In  the post “Using the @ToString annotation” you saw how easy it is to use @ToString. In this post I’ll show you how you can generate toString() methods in classes without using MainMethodBootstrap

The annoj.annotation.transformers package contains classes that are responsible for transforming class bytecode. Currently there’s one for toString() generation and onew for logging. In order to be able to instrument classes you have to instantiate TransformingClassLoader with a list of ClassTransformer objects and use that instance for loading the classes you want to modify. For example:

     List tl = Collections.singletonList(new ToStringCreator());
     TransformingClassLoader loader = new TransformingClassLoader(tl);
     loader.loadClass("Person");

This classloader modifies the classes if it finds the appropriate annotations. It’s important that the classloader should only be used in a so called bootstrap class which loads this classloader and then loads the classes with it.


How does AnnoJ work?

October 14, 2009

As you might guess AnnoJ modifies the bytecode of your classes. When TransformingClassLoader (the custom classloader used by AnnoJ) is asked to load a class it checks if its methods/fields or the entire class is annotated with @Loggable, @ToString or @Exclude. After that it tries to transform the bytecode of the class according to the annotations. If you use MainMethodBootstrap to start your application all classes are loaded with TransformingClassLoader.

AnnoJ uses javassist for bytecode modification. With this library it’s fairly easy to accomplish the neccessary transformations. For example the code below adds a logging method call to the beggining of the methods of a class called className:

     ClassPool pool = ClassPool.getDefault();
     CtClass ct = pool.get(className);
     CtMethod[] methods = ct.getDeclaredMethods();

     for (CtMethod method : methods) {
          method.insertBefore("{System.out.println(\"invoked: " + method.getName() + "(\" +  java.util.Arrays.asList($args) +\")\");}");
     }

     ct.toClass();

As you can see you don't have to bother yourself with the byecode - it can be modified at Java level. For more information consult the javassist documentation.


Why is AnnoJ useful?

October 14, 2009

Consider an application full of generated classes (by wsimport, for example). When many methods accepts this generated types as parameters logging/debugging is mere pain. If a method is called and you need to see the internal state of the paraeter object you have to work hard – unless you have an efficient tool for the job.

With @ToString annotation AnnoJ is able to generate toString() methods so you only have to print the parameters the object will show you its internal state.

Or think about logging. Sometimes it’s important to log when a method is called. How does AnnoJ help you? By using @Loggable annotation it can implement this kind (and many other kinds in the future) of logging.

.


Using the @Loggable annotation

October 14, 2009

With @Loggable you can tell AnnoJ to generate logging method calls at the beggining of some methods. For example if you have the following class definition

@Loggable
public class InterestingClass{
     public void importantMethod(){
          //some code here
     }
}

AnnoJ will modify importantMethod() to look like this:

importantMethod(){
     log("importantMethod called");
     //some code here
}

That is you can log when a method gets called without manually writing the logging code. What’s better AnnoJ will log the actual parameter values. Combined with the @ToString annotation this can be a powerful tool.

As with @ToString if you place @Loggable before a class definition then all methods will be logged. If you want to exclude some methods use the @Exclude annotation (just put it before the method you want to leave out). Alternatively you can specify in a String array which methods to include:

@Loggable(methods={"importantMethod"})
public class InterestingClass{
     public void importantMethod(){
          //other methods
     }
}

In this case only importantMethod() will be logged.

If you want that all methods of all classes of a package be logged just define a public class called PackageInfo and annotate it with @Loggable (you cant use parameters here).

The @Loggable annotation uses Log4j so you must provide a properly configured log4j.properties file in the root of your application.

As with the other annotations to use @Loggable you must bootstrap your application.


Using the @ToString annotation

October 14, 2009

One of the annotation types of AnnoJ is @ToString. With @ToString you can leave the implementation of the toString() methods to AnnoJ. After bootstrapping your application you can use toString() as if you implemented it manually.

To generate the toString() method in a class simply put the @ToString annotation just before the class definition:


@ToString
	public class InterestingClass{
		private Integer x;
                private String s;
                // more code
	}

After bootstrapping the application AnnoJ will generate a toString() method similar to this:


public String toString(){

return "[" + (this.x==null?"":"x="+this.x) + "," + (this.s==null?"":"s="+this.s) + "]";

}

If you want to leave x from the method just place the @Exclude annotation before the field:


@ToString
	public class InterestingClass{
                @Exclude
		private Integer x;
                private String s;
                // more code
	}

To generate a toString() method in all classes of a package you should define a public class named PackageInfo and annotate it with @ToString:


@ToString public class PackageInfo{}

In the future versions of AnnoJ there will be an option to leave out from toString() generation all fields with a given visiblity and you will be able to use the @Include annotation together with @ToString.


How to use AnnoJ?

October 14, 2009

As I mentioned in the previous post in AnnoJ everything’s implemented with annotations. More precisely: with runtime annotations. This means that you can place annotations on your packages (I will explain how), classes, methods and fields and these are evaluated at runtime. In the current version of AnnoJ the problem of runtime annotation evaluation is solved with the use of a custom classloader. You must load all your annotated classes with this classloader. To make this task easy AnnoJ defines the MainMethodBootstrap class. This class is responsible for the corrent processing of the annotations. The only thing you have to do is to tell it the name of the main class of your application and the command-line arguments you want to pass. An example:

 public class StarterClass{

		public static void main(String[] args){
			MainMethodBootstrap.callMainMethod("ClassWithMainMethod",
						 new String[]{"something"});
		}
	}

Here ClassWithMainMethod is the main class (ie. contains a public static void main(String[]) method).The command-line arguments to be passed to the main method are listed in the second argument of the callMainMethod() method. And that’s all!

One important thing is that because of the custom classloader at the moment AnnoJ can’t be used in application servers. Hopefully this will change in the future versions.


Introducing AnnoJ

October 14, 2009

AnnoJ is a small library containing tools for developers. With Annoj you can avoid writing boilerplate code in some cases. The current version of AnnoJ has to features:

  • automated generation of logging code
  • automated generation of toString() methods

As you can see, both features facilitate logging.

In AnnoJ everything is implemented with annotations. You can download the library from the sourceforge project page. To read the API documentation visit this link.