In one sententce, Annox allows you to load Java annnotations from XML resources instead reading them directly from packages, classes, fields, constructors and methods.
Java 1.5 introduced annotations, a general-purpose facility which allows you associate structured metadata with Java constructs (packages, classes, fields and so on). Java annotations are reflective in that they are embedded in class files generated by the compiler and may be retained by the Java VM to be made retrievable at run-time.
Java annotations is a very powerful and at the same time easy-to-use feature of the Java language. However, the fact that Java annotations must be defined and maintained in the source code of the program enforces certain limitations on the usage of annotations:
- You can't annotate third-party classes.
- You can't provide alternative annotations for already annotated classes.
- You can't provide different annotations for the same class.
Annox addresses these issues by providing a utilities which can load Java annotations from XML resources. Here's a small example.
Let's start with a normal annotation usage. Consider the following annotated class:
Typically you would read annotation from this class as follows:
Now assume that MyClass is a third-party class which can't be modified that easily. In this case you can't read annotations directly from MyClass or its fields, methods and so on. Here's how you can solve the task with Annox.
Annox reads annotations from XML resources associated with classes and packages. By default, class name is simply suffixed with the suffix .ann.xml. Default annotations resource for org.jvnet.annox.demos.guide.MyClass will be org/jvnet/annox/demos/guide/MyClass.ann.xml:
The namespace xmlns:g="http://annox.dev.java.net/org.jvnet.annox.demos.guide" associates the prefix g with the package org.jvnet.annox.demos.guide. Thus the element g:MyAnnotation defines an annotation of class org.jvnet.annox.demos.guide.MyAnnotation. The rest is quite self-explanatory.
The Annox-style code for reading annotations is just slightly different:
Annox is based on a very simple observation: Java annotations can be elegantly expressed as XML elements. Consider once again example from the introduction:
Even without any explanation you can easily read the following Java annotation:
Now, to be more specific, here are the rules:
- Namespace declaration associates namespace URI with package. In the example above
namespace http://annox.dev.java.net/org.jvnet.annox.demos.guide points to the package org.jvnet.annox.demos.guide. http://annox.dev.java.net/ is a standard prefix.
- Local name of the XML element defines local name of the annotation class. The g:MyAnnotation gives us MyAnnotation as local class name.
- Consequently, qualified name of the XML element provides the fully qualified name of the annotation class (org.jvnet.annox.demos.guide.MyAnnotation).
- Alternatively you can use annox:class attribute to provide the fully qualified name of the annotation class. This is useful if, for instance, Java class is not a valid XML name (like MyAnnotation$MyInnerClass).
- Values of annotation fields can be declared as attributes or as sub-elements. Simply use name of the field as local name of the attribute or sub-element.
- You may also use annox:field attribute of the sub-element to provide the name of the field. This is useful if you can't use the respective field name as the name of the XML element for some reasons.
- If your annotation has a sole value field, you can include the annotation value directly, without the surrounding <value>...</value> sub-element.
Following these rules you can express virtually any Java annotation in XML. And the best thing is that Annox can parse such XML elements and restore annotation instances automatically.
Let me give you a more extensive example. Consider the following annotation classes org.jvnet.annox.parser.tests.A, org.jvnet.annox.parser.tests.B and org.jvnet.annox.parser.tests.B$C (inner class of org.jvnet.annox.parser.tests.B):
Annotation classes presented above list almost all of the definition possibilities of annotation: primitive, string, enum, class and annotation fields with single or array cardinality. Here's an example of how an instance of the annotation org.jvnet.annox.parser.tests.A can be expressed in XML:
The corresponding annotation definition in Java would be as follows:
I hope you see how simple it is. Qualified name of the XML element points to the annotation class, attributes and sub-elements define values for annotation fields. And I'd like to repeat it once again:
|With Annox you can express virtually any Java annotation in XML and read virtually any annotation from XML elements.|
If your annotation has a single value field, you can use a laconic syntax when declaring it in your Java class. Assume you have the following annotation:
Verbose declaration syntax would be:
Java allows you to shorten it as follows:
Accordingly, with Annox you can use the verbose declaration:
As well as laconic syntax:
|This feature was introduced in version 0.4.4|
Java defines a special annotated element interface (java.lang.reflect.AnnotatedElement) which allows annotations to be read reflectively. This interface is implemented by java.lang.Package, java.lang.Class, java.lang.reflect.Field, java.lang.reflect.Constructor and java.lang.reflect.Method. This provides access to the annotations of the corresponding program element.
Annotated element factory introduces an additional abstraction layer. Instead of using packages, classes and so on as annotated elements directly, we may use the annotated element factory to load (read, construct, etc.) annotated element for the element of your program.
Using an intermediate factory allows you to implement an alternative strategy for loading annotations for your program elements. Here's a small example.
In the first case we use the class MyClass.class as annotation element. In this case we'll be able to access only those annotations which were defined in this class.
In the second case we use the annotation factory to read the annotated element for our class. In this case the factory has a chance to implement a different strategy for loading annotations. For instance, DualAnnotatedElementFactory used in this example will first check for annotations in corresponding XML resources and then, if no such annotations found, check the original program element (package, class, etc.) for annotations.
Since annotation access methods are defined in the AnnotatedElement interface, the way we access annotations of the annotated element is identical in both cases. Thus, usage of annotated element factory is non-intrusive for the rest of the code, introduces almost no overhead, but adds a degree of freedom.
|Constructor and method classes (java.lang.reflect.Constructor and java.lang.reflect.Method respectively) also implement the public Annotation getParameterAnnotations() method which provides access to the parameter annotations. Unfortunatelly, there's no appropriate interface (like ParameterizedAnnotatedElement) in Java. Annox defines such interface to model access to parameter annotations.|
Annox provides three implementations of the org.jvnet.annox.reflect.AnnotatedElementFactory interface:
- DirectAnnotatedElementFactory - trivial implementation, returns program elements as annotated elements.
- ResourcedAnnotatedElementFactory - reads annotations of the annotated elements from XML resources.
- DualAnnotatedElementFactory - contains two further annotated element factories - primary and secondary. This implementation first tries to read an annotation with the primary factory. If primary factory returns null, dual factory tries the secondary factory. By default, dual factory is configered with resourced factory as primary and direct factory as secondary factory.
Since direct and dual annotated element factories are trivial, further sections of this guide concentrate on the resourced factory.
In one of the previous sections we saw that Java annotations can be expressed in the form of XML elements. XML can also be used to associate such XML elements with target program items (packages, classes, etc.). Resourced factory is based on these two considerations. It loads XML annotation descriptors for packages and classes from the corresponding classpath resources, parses them and returns AnnotatedElement instances loaded from these resources.
By default, correspondence between packages, classes and XML annotation descriptors is defined by the simple convention:
- Package com.acme.foo - resource com/acme/foo/package-info.ann.xml.
- Class com.acme.foo.Bar - resource com/acme/foo/Bar.ann.xml.
It's as easy as .ann.xml suffix.
|There are no dedicated resources for fields, constructors and methods.|
Program elements are described in XML annotation descriptors using the package, class, field, constructor, method and parameter element of the http://annox.dev.java.net namespace. Blow is a pseudo-DTD definition of these elements:
The following two sections demonstrate the usage of these elements.
To demonstrate XML definition for class annotations, consider the following class:
This class has a value field, DemoClass() and DemoClass(int) constructors, getValue(), setValue(...) and add(...) methods.
Let's annotate this class, its fields, constructors, methods and parameters with the following trivial Comment annotation:
According to the default convention, XML annotation descriptor for our org.jvnet.annox.demos.guide.DemoClass must be defined in the org/jvnet/annox/demos/guide/DemoClass.ann.xml resource. Below is the listing of this annotation resource with explanation comments inline:
Just as we defined annotations for the org.jvnet.annox.demos.guide.DemoClass in the org/jvnet/annox/demos/guide/DemoClass.ann.xml resource, annotations for the org.jvnet.annox.demos.guide package can be defined in org/jvnet/annox/demos/guide/package-info.ann.xml:
Package definitions may also contain definitions for classes:
|Class resources (such as DemoClass.ann.xml) have higher priority than definitions in package resources (such as package-info.ann.xml). This means that you can define annotations for you classes in package resources and still be able to override them in class resources.|
In order to improve performance, Annox reader for XML annotation resources also implements caching. That is, when annotations for packages or classes are loaded for the first time, they will be cached internally and further on retrieved from the cache. The cache is based on weak hash maps to allow classes to be unloaded an avoid memory leaking.