Showing posts with label javassist. Show all posts
Showing posts with label javassist. Show all posts

Monday, March 16, 2009

Java annotation, scala object and javassist don't get along well

[Update: in the next paragraph, "showstopper" is clearly too strong - put it in my bad english level. The title is perhaps better, I wanted to say that things didn't came along as well as I hoped initially. In no case I wanted to sound over-pessimistic. Actually, the workaround is rather simple : I can use a direct call to the look-up service of T5 IOC each time I need a named service in place of the direct injection of that service.]

In my quest of using Tapestry 5 with a Scala back-end, I believe I fall on a showstopper. I seems that Javassist doesn't see annotation on method parameter in Scala object, although the annotation (being marked with Runtime retention) is seen with the Java reflexion API.

I tested with both Scala 2.7.3 and 2.8.0-nigtly, and both Javassist 3.9.0.GA and 3.10 - the behaviour is consistent in all combinations.

In fact, what seems to happens is that when one defines a Scala object "Foo.scala", scalac generates two classes : "Foo.class" that is a kind of singleton facade and initializer, and "Foo$.class" that is the actual class.

When the java reflexion API is used, I think that scala delegates the call in the rigth way. The problem seems to be that Javassist directly read the bytecode of "Foo.class", and doesn't see the annotation - it seems to be only on the method in "Foo$.class".

You can you see this behaviour like that:

Make a simple Java annotation, and compile it, something like @InjectService in Tapestry5-annotation package:


@Target({PARAMETER, FIELD})
@Retention(RUNTIME)
@Documented
public @interface InjectService
{
String value();
}



Create a scala class that make use of that annotation and test it visibility thanks to Java reflexion API and Javassist :



import org.apache.tapestry5.ioc.annotations.InjectService
import javassist._
import javassist.runtime._


object ObjectTestAnnotation {
def methodWithParamAnnot(@InjectService("service id") s:String) = s
}

class ClassTestAnnotation {
def methodWithParamAnnot(@InjectService("service id") s:String) = s
}

object Main {

def main(args: Array[String]) {
/*
* Using Java reflexion API
*/
val methodWithParamAnnot = ObjectTestAnnotation.
getClass.getMethod("methodWithParamAnnot", classOf[String])
val annotations = methodWithParamAnnot.getParameterAnnotations
println("* with java reflexion API, on ObjectTestAnnotation")
annotations.foreach(_.foreach( x => println(x)))
// print:
// * with java reflexion API, on ObjectTestAnnotation
// @org.apache.tapestry5.ioc.annotations.InjectService(value=service id)

val methodWithParamAnnot1 = classOf[ClassTestAnnotation].
getMethod("methodWithParamAnnot", classOf[String])
val annotations1 = methodWithParamAnnot1.getParameterAnnotations
println("* with java reflexion API, on ClassTestAnnotation")
annotations1.foreach(_.foreach( x => println(x)))
// print:
// * with java reflexion API, on ClassTestAnnotation
// @org.apache.tapestry5.ioc.annotations.InjectService(value=service id)

/*
* Using Javassist
*/
val cp = ClassPool.getDefault()
val ctString = cp.get("java.lang.String")
val methodWithParamAnnot2 = cp.get("test.ObjectTestAnnotation")
.getDeclaredMethod("methodWithParamAnnot",Array(ctString))
val annotations2 = methodWithParamAnnot2.getParameterAnnotations
println("* with javassist, on ObjectTestAnnotation")
annotations2.foreach(_.foreach( x => println(x)))

// print (AND THAT'S BAD, the annotation is not seen !)

// * with javassist, on ObjectTestAnnotation

val methodWithParamAnnot2_bis = cp.get("test.ObjectTestAnnotation$")
.getDeclaredMethod("methodWithParamAnnot",Array(ctString))
val annotations2_bis = methodWithParamAnnot2_bis.getParameterAnnotations
println("* with javassist, on ObjectTestAnnotation$")
annotations2_bis.foreach(_.foreach( x => println(x)))
// print:
// * with javassist, on ObjectTestAnnotation$
// @org.apache.tapestry5.ioc.annotations.InjectService(value="service id")


val methodWithParamAnnot3 = cp.get("test.ClassTestAnnotation")
.getDeclaredMethod("methodWithParamAnnot",Array(ctString))
val annotations3 = methodWithParamAnnot3.getParameterAnnotations
println("* with javassist, on ClassTestAnnotation")
annotations3.foreach(_.foreach( x => println(x)))
// print:
// * with javassist, on ClassTestAnnotation
// @org.apache.tapestry5.ioc.annotations.InjectService(value="service id")

println("done")


}

}



Cry ! Because, of course, Tapestry 5 uses these annotations in such a way that a bug here make the whole thing unusable - the core of T5-IOC container relies on them.
I would have to see if I can use a class in place of the object...

  © Blogger template 'Minimalist G' by Ourblogtemplates.com 2008

Back to TOP