-
1. Re: adding annotations
gkorland Feb 19, 2008 5:10 PM (in response to cat4hire)Hi Luca,
Check the AnnotationsAttribute JDoc.
ClassFile cf = ... ;
ConstPool cp = cf.getConstPool();
AnnotationsAttribute attr = new AnnotationsAttribute(cp, AnnotationsAttribute.visibleTag);
Annotation a = new Annotation("Author", cp);
a.addMemberValue("name", new
StringMemberValue("Chiba", cp));
attr.setAnnotation(a);
cf.addAttribute(attr);
cf.setVersionToJava5();
Guy -
2. Re: adding annotations
cat4hire Feb 20, 2008 8:51 AM (in response to cat4hire)Hi,
thanks for your reply, I was having a look at annotation_attribute, annotationwriter and annotationattribute, and now I've got a doubt: is the annotation only stored in the class file? Because what I'd like to obtain is to "inject" an annotation into a class (Runtime retention) when the class is loaded, without modifying the class file. But if I use the ClassFile object I have to modify the class file on the disk, is this correct? Is there a way to add an annotation at load time?
Thanks,
Luca -
3. Re: adding annotations
gkorland Feb 20, 2008 5:25 PM (in response to cat4hire)To add annotation to a class on load time the cleaner way to do it is to use javaagent.
See:
http://javahowto.blogspot.com/2006/07/javaagent-option.html
Guy -
4. Re: adding annotations
cat4hire Feb 21, 2008 4:09 AM (in response to cat4hire)I'm not sure about the use of an agent, because it will perform the annotation addition at the program load time, while I have to perform it depending on run-time conditions at class loading time, and most important I could have separate class loaders that add or not the annotations to the class they are loading. Is this possible with the agents?
-
5. Re: adding annotations
cat4hire Feb 22, 2008 2:38 AM (in response to cat4hire)Just to make it more clear: I've got a special class loader that is going to subclass a class that it must load to add specific behaviours, one of such behaviour should be an annotation. That is why I'm searching to do them thru Javassist.
Any suggestion? -
6. Re: adding annotations
cat4hire Feb 25, 2008 8:08 AM (in response to cat4hire)This is what I've tried to do in my class loader:
subProxyClassName += this.getSubClassNameSuffix(); // make a new class and prepare the annotation ClassFile classFile = new ClassFile( false, subProxyClassName, baseProxyClass.getName() ); ConstPool constantPool = classFile.getConstPool(); AnnotationsAttribute attribute = new AnnotationsAttribute( constantPool, AnnotationsAttribute.visibleTag ); javassist.bytecode.annotation.Annotation annotation = new javassist.bytecode.annotation.Annotation( this.annotationClassName, constantPool ); attribute.setAnnotation( annotation ); classFile.addAttribute( attribute ); classFile.setVersionToJava5(); classFile.setSuperclass( baseProxyClass.getName() ); ByteArrayOutputStream bos = new ByteArrayOutputStream(); DataOutputStream os = new DataOutputStream( bos ); classFile.write( os ); bytecode = bos.toByteArray(); return this.defineClass(subProxyClassName, bytecode, 0, bytecode.length);
The above is the findClass method of my class loader. The idea is that I have to create a subclass of the baseProxyClass (a CtClass object) and I have to add to it the annotation indicated by annotationClassName. When I return from the findClass method, the Class object I've got says me that it is a subclass of my proxy, but it cannot be instantiated as a proxy object (InstantiationError), as well as the getAnnotations() method returns an empty array.
What am I doing wrong?
Thanks,
Luca -
7. Re: adding annotations
cat4hire Feb 25, 2008 12:09 PM (in response to cat4hire)I've tried to define the class within the class loader, introspecting it in place to see what happens. In other words, I added the following code:
subProxyClassName += this.getSubClassNameSuffix(); // make a new class and prepare the annotation ClassFile classFile = new ClassFile( false, subProxyClassName, baseProxyClass.getName() ); ConstPool constantPool = classFile.getConstPool(); AnnotationsAttribute attribute = new AnnotationsAttribute( constantPool, AnnotationsAttribute.visibleTag ); javassist.bytecode.annotation.Annotation annotation = new javassist.bytecode.annotation.Annotation( this.annotationClassName, constantPool ); attribute.setAnnotation( annotation ); classFile.addAttribute( attribute ); classFile.setVersionToJava5(); classFile.setSuperclass( baseProxyClass.getName() ); ByteArrayOutputStream bos = new ByteArrayOutputStream(); DataOutputStream os = new DataOutputStream( bos ); classFile.write( os ); bytecode = bos.toByteArray(); baseProxyClass = null; Class clazz = this.defineClass(subProxyClassName, bytecode, 0, bytecode.length); System.out.println("Class " + clazz.getName()); System.out.println("Superclass " + clazz.getSuperclass()); System.out.println("Annotations"); try { AgentProxy aProxy = (AgentProxy) clazz.newInstance(); } catch (InstantiationException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalAccessException e) { // TODO Auto-generated catch block e.printStackTrace(); } for(Annotation a : clazz.getAnnotations()) System.out.println("annotazione " + a);
I believe that the way I'm using the ClassFile is wrong, since I'm not able to load the class, or better the class that is loaded is unable to be instantied as child of the parent class.
The class hierarchy is the following:
proxy
^
|
proxy_roled_1_24325842 <- when creating this class
I need to inject an annotationClass whitecat.proxy_roled_1_24325842 Superclass class whitecat.proxy Annotations java.lang.InstantiationException: whitecat.proxy_roled_1_24325842 at java.lang.Class.newInstance0(Class.java:340) at java.lang.Class.newInstance(Class.java:308) at whitecat.core.RoleEngine.findClass(RoleEngine.java:701) at whitecat.core.RoleEngine.injectAnnotation(RoleEngine.java:538) at whitecat.example.ExampleMain.run(ExampleMain.java:132) at java.lang.Thread.run(Thread.java:619)
Any suggestion about this? -
8. Re: adding annotations
cat4hire Feb 26, 2008 12:02 PM (in response to cat4hire)I've made another try: I've tried to get the classfile from a CtClass object instantied with the class name of the base class:
// make a new class and prepare the annotation CtClass newProxyClass = pool.makeClass(subProxyClassName); ClassFile classFile = newProxyClass.getClassFile(); //new ClassFile( false, subProxyClassName, baseProxyClass.getName() ); ConstPool constantPool = classFile.getConstPool(); AnnotationsAttribute attribute = new AnnotationsAttribute( constantPool, AnnotationsAttribute.visibleTag ); javassist.bytecode.annotation.Annotation annotation = new javassist.bytecode.annotation.Annotation( this.annotationClassName, constantPool ); attribute.setAnnotation( annotation ); classFile.addAttribute( attribute ); classFile.setVersionToJava5(); classFile.setName(subProxyClassName); classFile.setSuperclass( baseProxyClass.getName() ); ByteArrayOutputStream bos = new ByteArrayOutputStream(); DataOutputStream os = new DataOutputStream( bos ); classFile.write( os ); bytecode = bos.toByteArray();
but again, when I try to instantiate this class, that is of kind "proxy", I got an instantiation error:Class clazz = this.defineClass(subProxyClassName, bytecode, 0, bytecode.length); AgentProxy aProxy = (AgentProxy) clazz.newInstance();
I got the same instantiation exception of the previous post.
Any idea? -
9. Re: adding annotations
cat4hire Feb 29, 2008 4:32 AM (in response to cat4hire)I've found how to make the ClassFile working for defining my new class, but I'm still unable to add the annotation. Now I work as follows:
1) I create a CtClass object and set the inheritance chain on that;
2) I get the byte[] from the CtClass and place them into a ByteArrayInputStream, used to create a ClassFile;
3) I add the annotation to the classfile;
4) I get the byte[] from the classfile thru a ByteArrayOutputStream;
5) I load the class thru the defineClass method.
Now the class is of the right type, but it has no the annotation.
The following is my code; anyone has an idea of which is my error adding the annotation?subProxyClassName += this.getSubClassNameSuffix(); // make a new class and prepare the annotation CtClass newProxyClass = pool.makeClass(subProxyClassName); newProxyClass.setSuperclass( baseProxyClass ); ByteArrayInputStream is = new ByteArrayInputStream( newProxyClass.toBytecode() ); DataInputStream dis = new DataInputStream( is ); ClassFile classFile = new ClassFile( dis ); newProxyClass.defrost(); ConstPool constantPool = classFile.getConstPool(); AnnotationsAttribute attribute = new AnnotationsAttribute( constantPool, AnnotationsAttribute.visibleTag ); javassist.bytecode.annotation.Annotation annotation = new javassist.bytecode.annotation.Annotation( this.annotationClassName, constantPool ); attribute.setAnnotation( annotation ); classFile.addAttribute( attribute ); classFile.setVersionToJava5(); ByteArrayOutputStream bos = new ByteArrayOutputStream(); DataOutputStream os = new DataOutputStream( bos ); classFile.write( os ); new javassist.bytecode.annotation.Annotation( constantPool, subProxy ); bytecode = bos.toByteArray(); baseProxyClass = null; ... Class clazz = this.defineClass(subProxyClassName, bytecode, 0, bytecode.length);
Thanks,
Luca -
10. Re: adding annotations
cat4hire Feb 29, 2008 8:22 AM (in response to cat4hire)I've made another experiment: using an AnnotationsWriter as follows does not work too:
ByteArrayOutputStream aos = new ByteArrayOutputStream(); AnnotationsWriter aw = new AnnotationsWriter(aos, constantPool); aw.numAnnotations(1); aw.annotation(this.annotationClassName, 0); aw.close(); byte ab[] = aos.toByteArray(); attribute = new AnnotationsAttribute(constantPool, AnnotationsAttribute.visibleTag, ab); System.out.println("Annotation attribute " + attribute.getName() + " " + attribute.getAnnotations().length); classFile.addAttribute( attribute );
The class can be loaded and instantiated, but I cannot access its annotations.
In both cases, if I print the classfile content with a ClassFileWriter I got a message that lets me think I've got the annotations:attribute: SourceFile (2 byte): javassist.bytecode.SourceFileAttribute attribute: RuntimeVisibleAnnotations (6 byte): javassist.bytecode.AnnotationsAttribute
Moreover, in the case in which I use an AnnotationsWriter I got the following run-time exception when I try to access the annotations thru the Class.getAnnotations() method:Exception in thread "Thread-Example-Main" java.lang.reflect.GenericSignatureFormatError at sun.reflect.generics.parser.SignatureParser.error(SignatureParser.java:103) at sun.reflect.generics.parser.SignatureParser.parseFieldTypeSignature(SignatureParser.java:233) at sun.reflect.generics.parser.SignatureParser.parseTypeSignature(SignatureParser.java:359) at sun.reflect.generics.parser.SignatureParser.parseTypeSig(SignatureParser.java:157) at sun.reflect.annotation.AnnotationParser.parseSig(AnnotationParser.java:367) at sun.reflect.annotation.AnnotationParser.parseAnnotation(AnnotationParser.java:181) at sun.reflect.annotation.AnnotationParser.parseAnnotations2(AnnotationParser.java:69) at sun.reflect.annotation.AnnotationParser.parseAnnotations(AnnotationParser.java:52) at java.lang.Class.initAnnotationsIfNecessary(Class.java:3072) at java.lang.Class.getAnnotations(Class.java:3052)
I cannot find whre I'm doing something wrong. -
11. Re: adding annotations
cat4hire Mar 5, 2008 6:59 AM (in response to cat4hire)I'm going mad, anyone with a suggestion?
I've found that the following piece of code:ByteArrayOutputStream aos = new ByteArrayOutputStream(); AnnotationsWriter aw = new AnnotationsWriter(aos, constantPool); aw.numAnnotations(1); aw.annotation(this.annotationClassName, 0); aw.close(); byte ab[] = aos.toByteArray(); System.out.println("\n\t++++Annotation bytecode for annotation " + this.annotationClassName + "++++"); System.out.println(new String(ab) ); System.out.println("\n\t++++++++++++++\n"); attribute = new AnnotationsAttribute(constantPool, AnnotationsAttribute.visibleTag, ab);
that should create the annotation specified, prints the following:++++Annotation bytecode for annotation whitecat.example.DBRoleAnnotation++++ +++++++++++++++++++++++
So I'm pretty sure that the bytecode generated for the annotation is empty, and that the annotation is regularly added to my class, but without a bytecode I cannot access it. Anyone can explain why the bytecode is null? -
12. Re: adding annotations
cat4hire Mar 12, 2008 7:52 AM (in response to cat4hire)Hi,
I'm still fighting with the annotation problem. I've made a code clean up, and now I've got the following piece of code:// make a new class CtClass newProxyClass = pool.makeClass( subProxyClassName ); newProxyClass.setSuperclass( baseProxyClass ); // place a default constructor in the new class CtConstructor constructor = new CtConstructor(null, newProxyClass ); constructor.setBody(";"); newProxyClass.addConstructor(constructor); // get the class file and add the annotation ClassFile classFile = newProxyClass.getClassFile(); ConstPool constantPool = classFile.getConstPool(); AnnotationsAttribute attr = new AnnotationsAttribute(constantPool, AnnotationsAttribute.visibleTag); javassist.bytecode.annotation.Annotation a = new javassist.bytecode.annotation.Annotation(this.annotationClassName, constantPool); attr.setAnnotation(a); classFile.addAttribute(attr); classFile.setVersionToJava5(); // transform the classfile into bytecode ByteArrayOutputStream bos = new ByteArrayOutputStream(); DataOutputStream os = new DataOutputStream( bos ); classFile.write( os ); os.close(); // load the class return this.defineClass(subProxyClassName, bytecode, 0, bytecode.length);
at runtime no exception are thrown, but the new class still has no annotation added!
Then I saved the bytecode to a file in two cases, the above piece of code and the one without the addAttribute, and I can see they are different, so it seems as the annotation is added, but it is not visible to the runtime system.
Please, any suggestion is appreciated!!!
Thanks,
Luca -
13. Re: adding annotations
cat4hire Apr 1, 2008 10:40 AM (in response to cat4hire)After a lot I found that the following is the code that works for me:
ClassFile classFile = newProxyClass.getClassFile(); ConstPool constantPool = classFile.getConstPool(); AnnotationsAttribute attr = new AnnotationsAttribute(constantPool, AnnotationsAttribute.visibleTag); javassist.bytecode.annotation.Annotation a = new javassist.bytecode.annotation.Annotation(this.annotationClassName, constantPool); attr.setAnnotation(a); classFile.addAttribute(attr); classFile.setVersionToJava5(); ByteArrayOutputStream bos = new ByteArrayOutputStream(); DataOutputStream os = new DataOutputStream( bos ); classFile.write( os ); os.close(); // all done return bos.toByteArray();
the above produces the byte code for a class with the added annotation.
Thanks,
Luca -
14. Re: adding annotations
paulkeeble Jul 4, 2009 9:57 PM (in response to cat4hire)The beauty of a Java agent is it gets passed every loaded class, regardless of classloader. So although you need to filter the list as quickly as possible (you'll get thousands of calls from java.* classes for instance) it is the best way to do the instrumentation.
I have happily got annotation adding working in my code base recently so thought it might help you if I explained roughly how I do it.
First up getting a CtClass:
byte[] byteCode = ....
ClassPool pool = ....
CtClass clazz = pool.makeClass(new ByteArrayInputStream(byteCode));
If your using a Java agent it will give you the bytes directly, if you load them yourself in a custom classloader.
Next up you the annotations adding to a CtClass:
CtClass clazz = ....
ClassFile classFile = clazz.getClassFile();
ConstPool cp = classFile.getConstPool();
AnnotationsAttribute attr = new AnnotationsAttribute(cp, AnnotationsAttribute.visibleTag);
Annotation theAnnotation = new Annotation("org.package.AnnotationType",cp);
annotation.addMemberValue("name", new StringMemberValue("Paul",cp));
attr.setAnnotation(theAnnotation);
clazz.getClassInfo().addAttribute(attr);
That worked well for me as an approach. If you want to see a working version (the far more complicated version based on methods and arrays of values) you can see the latest version of my code at JEMM Git MethodAnnotationTransformation.java