Despite the fact that it has been a while since the release of JDK 5, there are projects that still require backwards compatibility with JDK 1.4.2. This is relatively common, and the reasons for it are diverse. To deal with this type of requirement, JBoss AOP provides two different solutions. By using one of them, you can use all features JBoss AOP provides, including annotation-related features, and still keep your code backwards compatible with JDK1.4.2.
The first solution to achieve JDK 1.4.2 backwards compatibility is to use the JBoss Retro tool. JBoss Retro converts JDK 5 compiled bytecodes into bytecodes that can be run using a JDK 1.4.2 virtual machine.
This tool acts as a bytecode processor, and using it is very simple. You just need to write your code using JDK 5 constructs and features, and then compile it, using JDK 5. Next, process the generated bytecodes using JBoss Retro tool (just like you do when you use aopc compiler), and now your bytecodes are ready to run using a JDK 1.4.2 virtual machine. Notice this solution allows not only the use of JBoss AOP annotations, but of most JDK 5 features and new API operations.
The simplest way to run JBoss Retro is using the ant task. You just need to declare it:
<taskdef name="retro" classname="org.jboss.ant.tasks.retro.Retro" classpathref="jboss.retro.classpath"/>
And use it as in the following example:
<retro compilerclasspathref="jboss.retro.classpath" destdir="."> <classpath refid="jboss.retro.classpath"/> <classpath path="."/> <src path="."/> </retro>
This task takes the following parameters:
It is also possible to run JBoss Retro with the following command line:
$ java -cp <all the JBoss AOP jars and the directory containing files we want to AOP> \ -verbose <true/false> -suppress <true/false> -destdir < org.jboss.ant.tasks.retro.Weaver \ [-bytecode]<files>+
JBoss Retro is the de facto standard solution JBoss Group provides to achieve JDK 1.4.2 backward compatibility. However, if you do not have the option to compile your code using JDK 5, you should go with the next solution, the annotation compiler.
Unlike JBoss Retro, the annotation compiler does not support all JDK 5 constructs and new APIs. Its functionality consists in supporting only annotations, that must be written in the form of doclets. Nevertheless, this is enough to allow the use of all JBoss AOP features, and doesn't require a JDK 5 compiler.
This way, if you can't use a JDK 5 compiler to compile your code, you should stick with the annotation compiler. It will process your application's bytecodes, transforming doclets into annotations. The result of this transformation is that your doclets will become viewable by JBoss AOP as if they were regular JDK 5 annotations.
In the next sections, we will see what is the format your doclets need to follow in order to be transformed into annotations, and how to use the annotation compiler.
In JDK 5, annotations must map to an annotation type, which is defined using the following syntax:
package com.mypackage; public @interface MyAnnotation { String myString(); int myInteger(); }
Similarly, annotations for use with the annotation compiler also need to map to a type. And this one is defined in exactly the same way as above, with the important difference that '@interface' is replaced by 'interface'. i.e. the simulated annotation type is a normal Java interface:
package com.mypackage; public interface MyAnnotation extends org.jboss.lang.Annotation { String myString(); int myInteger(); }
One difference from AOP 1.x is that the interfaces defining the annotations must now extend org.jboss.lang.Annotation, this is because JBoss Retro is now the primary mechanism for using annotations in JDK 1.4.2.
The syntax for using annotations in JDK 1.4.2 is almost exactly the same as JDK 5 annotations except for these subtle differences:
This example shows an annotated class in JDK 1.4.2:
package com.mypackage; /** * @@com.mypackage.MyAnnotation (myString="class", myInteger=5) */ public class MyClass { /** * @@com.mypackage.MyAnnotation (myString="field", myInteger=4) */ private String myField; /** * @@com.mypackage.MyAnnotation (myString="constructor", myInteger=3) */ public MyClass() { } /** * @@com.mypackage.MyAnnotation (myString="method", myInteger=3) */ public int myMethod() { } }
The next aspect is the JDK1.4.2 version of the @Introduction exammple (Chapter 6, Annotation Bindings). Notice the slight difference in the JDK 1.4.2 annotation: class values don't have the ".class" suffix:
package com.mypackage; /* * @@org.jboss.aop.Aspect (scope = Scope.PER_VM) */ public class IntroAspect { /* * @org.jboss.aop.Introduction (target=com.blah.SomeClass, \ interfaces={java.io.Serializable}) */ public static Object pojoNoInterfacesIntro; }
Now, look at the next example:
package com.mypackage; import org.jboss.aop.introduction.AnnotationIntroduction; /** * @@org.jboss.aop.InterceptorDef (scope=org.jboss.aop.advice.Scope.PER_VM) * @@org.jboss.aop.Bind (pointcut="all(com.blah.SomePOJO)") */ public class IntroducedAnnotationInterceptor implements Interceptor { /** * @@org.jboss.aop.AnnotationIntroductionDef \ (expr="method(* com.blah.SomePOJO->annotationIntroductionMethod())", \ invisible=false, \ annotation="@com.mypackage.MyAnnotation \ (string='hello', integer=5, bool=true)") */ public static AnnotationIntroduction annotationIntroduction; public String getName() { return "IntroducedAnnotationInterceptor"; } public Object invoke(Invocation invocation) throws Throwable { return invocation.invokeNext(); } }
The code above is the jdk1.4.2 version equivalent to the @AnnotationIntroductionDef example we have seen in Chapter 6, Annotation Bindings. Note that, in the version above, the reference to only uses one '@'. In addition,the value for its string parameter uses single quotes instead of double ones.
Another JDK 5 feature that JBoss AOP helps introduce to JBoss 1.4.2 are Enums. As an example we can look at the org.jboss.aop.advice.Scope enum that is used for the @Aspect annotation. Here is the JDK 5 version.
package org.jboss.aop.advice; public enum Scope { PER_VM, PER_CLASS, PER_INSTANCE, PER_JOINPOINT }
And it's usage in JDK 5
package com.mypackage; @Aspect (scope=org.jboss.aop.advice.Scope.PER_VM) public class SomeAspect { }
The usage in JDK 1.4.2 is similar:
package com.mypackage; /** * @@org.jboss.aop.Aspect (scope=org.jboss.aop.advice.Scope.PER_VM) */ public class SomeAspect { //... }
However the declaration of the enumeration is different in 1.4.2:
package org.jboss.aop.advice; import java.io.ObjectStreamException; public class Scope extends org.jboss.lang.Enum { private Scope(String name, int v) { super(name, v); } public static final Scope PER_VM = new Scope("PER_VM", 0); public static final Scope PER_CLASS = new Scope("PER_CLASS", 1); public static final Scope PER_INSTANCE = new Scope("PER_INSTANCE", 2); public static final Scope PER_JOINPOINT = new Scope("PER_JOINPOINT", 3); private static final Scope[] values = {PER_VM, PER_CLASS, PER_INSTANCE, PER_JOINPOINT}; Object readResolve() throws ObjectStreamException { return values[ordinal]; } }
To create your own enum class for use within annotations, you need to inherit from org.jboss.lang.Enum. Each enum has two values, a String name, and an integer ordinal. The value used for the ordinal must be the same as it's index in the static array.
The annotation compiler allows you to use annotations within annotations. This is best illustrated with an example. The definitions of the annotation interfaces in JDK 1.4.2:
com.mypackage; public interface Outer { Inner[] values(); }
com.mypackage; public interface Inner { String str(); int integer(); }
The annotations can be applied as follows
com.mypackage; /** * @@com.mypackage.Outer ({@@com.mypackage.Inner (str="x", integer=1), \ @@com.mypackage.Inner (str="y", integer=2)}) */ public class Test { Inner[] values(); }
In order to use the JDK 1.4.2 annotations you have to precompile your files with an annotation compiler.
To use the annotation compiler you can create a simple ant build.xml file
<?xml version="1.0" encoding="UTF-8"?> <project default="run" name="JBoss/AOP"> <target name="prepare">
Include the jars AOP depends on
<path id="javassist.classpath"> <pathelement path="../../../javassist.jar"/> </path> <path id="trove.classpath"> <pathelement path="../../../trove.jar"/> </path> <path id="concurrent.classpath"> <pathelement path="../../../concurrent.jar"/> </path> <path refid="jboss.common.core.classpath"/> <path refid="jboss.common.logging.spi.classpath"/> <path refid="jboss.common.logging.log4j.classpath"/> <path refid="jboss.common.logging.jdk.classpath"/> <pathelement path="../../../jboss-common.jar"/> </path> <path id="jboss.aop.classpath"> <pathelement path="../../../jboss-aop.jar"/> </path> <path id="qdox.classpath"> <pathelement path="../../../qdox.jar"/> </path> <path id="classpath"> <path refid="javassist.classpath"/> <path refid="trove.classpath"/> <path refid="jboss.aop.classpath"/> <path refid="jboss.common.core.classpath"/> <path refid="jboss.common.logging.spi.classpath"/> <path refid="jboss.common.logging.log4j.classpath"/> <path refid="jboss.common.logging.jdk.classpath"/> <path refid="concurrent.classpath"/> <path refid="qdox.classpath"/> </path>
Define the ant task that does the annnotation compilation
<taskdef name="annotationc" classname="org.jboss.aop.ant.AnnotationC" classpathref="jboss.aop.classpath"/> </target> <target name="compile" depends="prepare">]></programlisting> Compile the source files <programlisting><!CDATA[ <javac srcdir="." destdir="." debug="on" deprecation="on" optimize="off" includes="**"> <classpath refid="classpath"/> </javac>
Run the annotation compiler
<annotationc compilerclasspathref="classpath" classpath="." bytecode="true"> <src path="."/> </annotationc> </target> </project>
The org.jboss.aop.ant.AnnotationC ant task takes several parameters.
You cannot currently specify both bytecode and xml.
You can also run org.jboss.aop.ant.AnnotationC from the command line, you need
$ java -cp <all the JBoss AOP jars and the directory containing files we want to AOP> \ org.jboss.aop.annotation.compiler.AnnotationCompiler \ [-xml [-o outputfile ]] [-bytecode]<files>+
In the /bin folder of the distribution we have provided batch/script files to make this easier. It includes all the aop libs for you, so you just have to worry about your files. The usage is:
$ annotationc <classpath> [-verbose] [-xml [-o outputfile]] [-bytecode] <dir_or_file>+
The other parameters are the same as above.