Annotations are a new feature from Java 5. Annotations are a kind of comment or meta data you can insert in your Java code. But how can we add them dynamically at runtime to the java class since the jdk doesn’t provide any addAnnotation method ?
Introduction
Java annotations were introduced in 2002 through the JCP (JSR-175) and were approved inSeptember 2004 as an alternative to the configuration xml files. Java annotations, can (if necessary) be accessible to the programmer at the runtime through reflection api.
How to scan java annotations?
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 @Retention (value = RetentionPolicy.RUNTIME), and loading each resource into memory will consume an unnecessary amount of memory.
Lets create a simple annotation, annotations are defined like interfaces. Here is the @PersonneName definition:
package sample;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(value = RetentionPolicy.RUNTIME) //The annotation is saved in the*.class and can be used by the JVM.
@Target(value = ElementType.METHOD) //The annotation can be used on methods.
public @interface PersonneName {
public String name();
}
let's now create a simple SayHelloBean on wich we will apply this simple annotation
package sample;
import java.lang.reflect.Method;
import sample.PersonneName;
public class SayHelloBean {
private static final String HELLO_MSG = "Hello ";
@PersonneName(name="World !! (simple annotation)")
public String sayHelloTo(String name){
return HELLO_MSG+name;
}
public static void main(String[] args) {
try{
//instanciate the bean
SayHelloBean simpleBean = new SayHelloBean();
//get the method descriptor through reflection
Method helloMessageMethod = simpleBean.getClass().getDeclaredMethod("sayHelloTo", String.class);
//scan the annotation
PersonneName mySimpleAnnotation = (PersonneName) helloMessageMethod.getAnnotation(PersonneName.class);
System.out.println(simpleBean.sayHelloTo(mySimpleAnnotation.name()));
}
catch(Exception e){
e.printStackTrace();
}
}
}
Runing the main method produce : Hello World !! (simple annotation)
How to add Annotations dynamically at Runtime to a java class method?
Why ?
- Jdk doesn’t provide an addAnnotation method through reflection.
- Sometimes we need to define annotation dynamically at runtime (example of jsr 303 validation annotations )
- Add new behaviors to your classes
How ?
We will use Javassist (Java Programming Assistant) . It is a class library for editing bytecodes in Java; it enables Java programs to define a new class at runtime and to modify a class file when the JVM loads it. (More informations and downloads here javassist ).
Now let’s modify the preceding example to add the @PersonneName at runtime to the SayHelloBean,first we will delete the annotation from the SayHelloBean code
package sample;
public class SayHelloBean {
private static final String HELLO_MSG = "Hello ";
public String sayHelloTo(String name){
return HELLO_MSG+name;
}
}
second we create the class AddRunTimeAnnotation that will add the annotation dynamically to the SayHelloBean class :
package sample;
import java.lang.reflect.Method;
import sample.PersonneName;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.bytecode.AnnotationsAttribute;
import javassist.bytecode.ClassFile;
import javassist.bytecode.ConstPool;
import javassist.bytecode.annotation.Annotation;
import javassist.bytecode.annotation.StringMemberValue;
public class AddRunTimeAnnotation {
public static void addPersonneNameAnnotationToMethod(String className,String methodName) throws Exception{
//pool creation
ClassPool pool = ClassPool.getDefault();
//extracting the class
CtClass cc = pool.getCtClass(className);
//looking for the method to apply the annotation on
CtMethod sayHelloMethodDescriptor = cc.getDeclaredMethod(methodName);
// create the annotation
ClassFile ccFile = cc.getClassFile();
ConstPool constpool = ccFile.getConstPool();
AnnotationsAttribute attr = new AnnotationsAttribute(constpool, AnnotationsAttribute.visibleTag);
Annotation annot = new Annotation("sample.PersonneName", constpool);
annot.addMemberValue("name", new StringMemberValue("World!! (dynamic annotation)",ccFile.getConstPool()));
attr.addAnnotation(annot);
// add the annotation to the method descriptor
sayHelloMethodDescriptor.getMethodInfo().addAttribute(attr);
// transform the ctClass to java class
Class dynamiqueBeanClass = cc.toClass();
//instanciating the updated class
SayHelloBean sayHelloBean = (SayHelloBean) dynamiqueBeanClass.newInstance();
try{
Method helloMessageMethod = sayHelloBean.getClass().getDeclaredMethod(methodName, String.class);
//getting the annotation
PersonneName personneName = (PersonneName) helloMessageMethod.getAnnotation(PersonneName.class);
System.out.println(sayHelloBean.sayHelloTo(personneName.name()));
}
catch(Exception e){
e.printStackTrace();
}
}
public static void main(String[] args) {
try {
AddRunTimeAnnotation.addPersonneNameAnnotationToMethod("sample.SayHelloBean", "sayHelloTo");
} catch (Exception e) {
e.printStackTrace();
}
}
}
Runing the main method now produce : Hello World!! (dynamic annotation).
Looking ahead
There's a lot more to Javassist than what we've covered in this article. Javassist enables Java programs to define a new class at runtime and to modify a class file before the JVM loads it. Unlike other similar systems, Javassist provides source-level abstraction; programmers can modify a class file without detailed knowledge of the Java bytecode.start enjoying it :).
Ressources :