Monday, January 3, 2011

How to add Annotations at Runtime to a java class method using Javassist? (Part 1 )

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 :

23 comments:

  1. Great article with great examples ! i think it would be better to explain more How to implement this ...

    ReplyDelete
  2. thanks for sharing your code

    ReplyDelete
  3. thanks ,it helped me .

    ReplyDelete
  4. hi,

    I am getting following error while executing "Class dynamiqueBeanClass = cc.toClass();"

    Error:
    javassist.CannotCompileException: by java.lang.LinkageError attempted duplicate class definition for name

    Please help me out.

    Thanks

    ReplyDelete
  5. Hi and sorry for my late response :
    The JVM does not allow dynamically reloading a class. Thus, you cannot alter the definition of a class after the JVM loads it. However if you want to do so you have to create your own classloader because in java, multiple class loaders can coexist and each class loader creates its own name space check this :
    http://www.csg.is.titech.ac.jp/~chiba/javassist/tutorial/tutorial.html#load

    ReplyDelete
  6. Hi there,

    Have following -
    jvm arguments -Xmx512m -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8000


    private static final String hotSwapPort = "8000";
    ...
    HotSwapper hotSwapper = new HotSwapper(hotSwapPort);
    byte[] classBytes = ctClass.toBytecode();
    hotSwapper.reload(className, classBytes);

    But end up getting the following error -

    java.net.ConnectException: Connection refused: connect
    at java.net.PlainSocketImpl.socketConnect(Native Method)
    at java.net.PlainSocketImpl.doConnect(PlainSocketImpl.java:333)
    at java.net.PlainSocketImpl.connectToAddress(PlainSocketImpl.java:195)


    I am able to connect to the port via eclipse remote debug and have no firewall.

    Any insights would be helpful.

    Thanks in advance.

    ReplyDelete
  7. very good article.. thanks for sharing

    ReplyDelete
  8. Aww. This is awesome. Thank you!

    ReplyDelete
  9. This will be awesome... if works...
    javassist.CannotCompileException: by java.lang.LinkageError attempted duplicate class definition for name

    ReplyDelete
  10. I want to add the annotation to the Class instead the method, what should i done after
    CtClass cc = pool.getCtClass(className);

    ReplyDelete
  11. I am getting following error while executing "Class dynamiqueBeanClass = cc.toClass();"

    Error:
    javassist.CannotCompileException: by java.lang.LinkageError attempted duplicate class definition for name

    Please help me out.

    ReplyDelete
  12. Hi,
    Thanks for sharing of your article you are given a valuable information. Java programming was released by sun micro-system.

    ReplyDelete
  13. I love reading through your blog and look forward to all your posts! Keep up the great work!
    บาคาร่า
    จีคลับ
    gclub

    ReplyDelete
  14. มาเล่น”สล็อต” กันดีกว่า

    เนื่องจากเป็นการเล่นพนันที่ใช้เงินลงทุนน้อย แต่มีโอกาสชนะเดิมพันที่มีทั้งรางวัลเล็กและรางวัลใหญ่ได้เงินเป็นจำนวนมาก ได้ลุ้นระทึก สร้างความเร้าใจตลอดช่วงเวลาที่วงล้อของสล็อตหมุนไป ปัจจุบันปัจจุบันเทคโนโลยีสื่อสารที่ทันสมัย ทำให้ เกมส์สล็อต ได้รับการพัฒนาและยกระดับ จากที่เคยเล่นกันแพร่หลายเฉพาะในสถาน ให้สามารถเล่นกันทางออนไลน์ได้แล้ว

    โดยการเล่นผ่านเว็บไซต์ผู้ให้บริการ ออนไลน์ หรือแอพริเคชั่นบนมือถือ ซึ่งการเล่น เกมสล็อตออนไลน์ ได้รับความนิยมไม่ยิ่งหย่อนไปกว่าการเล่นสล็อตในสถาน หรืออาจจะได้ว่าได้รับความนิยมมากกว่าในสถานก็ว่าได้ เนื่องจากอำนวยสะดวกให้แก่ผู้ที่ชื่นชอบการเล่นสล็อตสามารถเล่นได้ทุกที่ ทุกเวลา ไม่พลาดโอกาสการทำกำไรได้ทั้งวันทั่งคืน ปัจจุบันสล็อตออนไลน์ เป็นหนึ่งในเกมส์พนันออนไลน์ที่ได้รับความนิยมสูงมาก


    สล็อต

    ReplyDelete
  15. Thanks for taking the time to discuss this, I feel strongly about it and love learning more on this topic.
    Java代写

    ReplyDelete
  16. https://www.blogger.com/comment.g?blogID=8417362&postID=5012066968442970822&page=1&token=1580798459827&isPopup=true

    ReplyDelete
  17. Thank you for the useful educational article
    Sa gaming

    ReplyDelete
  18. Excellent blog, I wish to share your post with my folks circle. It’s really helped me a lot, so keep sharing post like this
    DevOps Training in Chennai

    DevOps Course in Chennai



    ReplyDelete