-
Notifications
You must be signed in to change notification settings - Fork 99
Annox User Guide
In one sentence, 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: org/jvnet/annox/demos/guide/MyClass.java
package org.jvnet.annox.demos.guide;
@MyAnnotation(printName = "My class")
public class MyClass {
@MyAnnotation(printName = "My field")
public String myField;
}
Typically you would read annotation from this class as follows:
final AnnotatedElement myClass = MyClass.class;
final AnnotatedElement myField = MyClass.class.getDeclaredField("myField");
final MyAnnotation myClassAnnotation = myClass.getAnnotation(MyAnnotation.class);
final MyAnnotation myFieldAnnotation = myField.getAnnotation(MyAnnotation.class);
assertEquals("My class", myClassAnnotation.printName());
assertEquals("My field", myFieldAnnotation.printName());
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
:
<class xmlns="http://annox.dev.java.net"
xmlns:g="http://annox.dev.java.net/org.jvnet.annox.demos.guide">
<!-- Just like @MyAnnotation(printName = "My annotated class") -->
<g:MyAnnotation printName="My annotated class"/>
<field name="myField">
<!-- Just like @MyAnnotation(printName = "My annotated field") -->
<g:MyAnnotation printName="My annotated field"/>
</field>
</class>
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:
final AnnotatedElementFactory aef = new DualAnnotatedElementFactory();
final AnnotatedElement myClass = aef.getAnnotatedElement(MyClass.class);
final AnnotatedElement myField = aef.getAnnotatedElement(MyClass.class.getDeclaredField("myField"));
final MyAnnotation myClassAnnotation = myClass.getAnnotation(MyAnnotation.class);
final MyAnnotation myFieldAnnotation = myField.getAnnotation(MyAnnotation.class);
assertEquals("My annotated class", myClassAnnotation.printName());
assertEquals("My annotated field", myFieldAnnotation.printName());
Annox is based on a very simple observation: Java annotations can be elegantly expressed as XML elements. Consider once again example from the introduction:
<g:MyAnnotation
xmlns:g="http://annox.dev.java.net/org.jvnet.annox.demos.guide"
printName="My annotated field"
/>
Even without any explanation you can easily read the following Java annotation:
@org.jvnet.annox.demos.guide.MyAnnotation(printName="My annotated field")
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 packageorg.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 usMyAnnotation
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 (likeMyAnnotation$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
):
package org.jvnet.annox.parser.tests;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface A {
long longField();
int intField();
short shortField();
char charField();
byte byteField();
double doubleField();
float floatField();
boolean booleanField();
String stringField();
E enumField();
Class classField();
B annotationField();
}
package org.jvnet.annox.parser.tests;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface B {
long[] longArrayField();
int[] intArrayField();
short[] shortArrayField();
char[] charArrayField();
byte[] byteArrayField();
double[] doubleArrayField();
float[] floatArrayField();
boolean[] booleanArrayField();
String[] stringArrayField();
E[] enumArrayField();
Class[] classArrayField();
B.C[] annotationArrayField();
@Retention(RetentionPolicy.RUNTIME)
public static @interface C {
}
}
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 :
<A xmlns="http://annox.dev.java.net/org.jvnet.annox.parser.tests"
xmlns:annox="http://annox.dev.java.net"
booleanField="false"
byteField="0"
charField="a"
classField="java.lang.String"
doubleField="1"
enumField="ONE"
floatField="2.3"
intField="4"
longField="5"
shortField="6"
stringField="7">
<annotationField>
<B
booleanArrayField="false true"
byteArrayField="0 1"
charArrayField="a b"
classArrayField="java.lang.String java.lang.Boolean"
doubleArrayField="2 3"
enumArrayField="ONE TWO"
floatArrayField="4.5 6.7"
intArrayField="8 9"
longArrayField="10 11"
shortArrayField="12 13"
stringArrayField="14 15">
<stringArrayField>16</stringArrayField>
<stringArrayField>17</stringArrayField>
<q annox:field="annotationArrayField">
<B.C/>
<C annox:class="org.jvnet.annox.parser.tests.B$C"/>
</q>
</B>
</annotationField>
</A>
The corresponding annotation definition in Java would be as follows :
@A(
booleanField = false,
byteField = 0,
charField = 'a',
classField = String.class,
doubleField = 1,
enumField = E.ONE,
floatField = 2.3f,
intField = 4,
longField = 5,
shortField = 6,
stringField = "7",
annotationField = @B(
booleanArrayField = { false, true },
byteArrayField = { 0, 1 },
charArrayField = { 'a', 'b' },
classArrayField = { String.class, Boolean.class },
doubleArrayField = { 2, 3 },
enumArrayField = { E.ONE, E.TWO },
floatArrayField = { 4.5f, 6.7f },
intArrayField = { 8, 9 },
longArrayField = { 10, 11 },
shortArrayField = { 12, 13 },
stringArrayField = { "14", "15", "16", "17" },
annotationArrayField = { @B.C, @B.C }
)
)
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: Icon
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 :
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String value();
}
Verbose declaration syntax would be :
@MyAnnotation(value = "my value")
public class MyClass { ... }
Java allows you to shorten it as follows :
@MyAnnotation("my value")
public class MyClass { ... }
Accordingly, with Annox you can use the verbose declaration :
<ma:MyAnnotation xmlns:ma="...">
<value>my value</value>
</ma:MyAnnotation>
As well as laconic syntax:
<ma:MyAnnotation xmlns:ma="...">my value</ma:MyAnnotation>
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.
// Direct access to annotated element
final AnnotatedElement myClass = MyClass.class;
myClass.getAnnotation(MyAnnotations.class);
// Indirect access to annotated element via the annotated element factory
final AnnotatedElementFactory = new DualAnnotatedElementFactory();
final AnnotatedElement annotatedElementForMyClass =
annotatedElementFactory.getAnnotatedElement(MyClass.class);
annotatedElementForMyClass.getAnnotation(MyAnnotations.class);
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.
Icon
Constructor and method classes (
java.lang.reflect.Constructor
andjava.lang.reflect.Method
respectively) also implement thepublic Annotation[][] getParameterAnnotations()
method which provides access to the parameter annotations. Unfortunatelly, there's no appropriate interface (likeParameterizedAnnotatedElement
) 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
- resourcecom/acme/foo/package-info.ann.xml
. - Class
com.acme.foo.Bar
- resourcecom/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. Below is a pseudo-DTD definition of these elements :
// Namespace http://annox.dev.java.net
package ( /* package annotation elements, */
// Optional name of the package
@name,
// Classes
class*
)
class ( /* class annotation elements, */
// Optional name of the class
@name,
// Fields
field*,
// Constructors,
constructor*,
// Methods
method*
)
field ( /* field annotation elements, */
// Name of the field
@name
)
constructor ( /* constructor annotation elements, */
// Optional constructor arguments signature
@arguments,
// Constructor parameters
parameter*
)
method ( /* method annotation elements */
// Optional method arguments signature
@arguments,
// Method parameters
parameter*
)
parameter ( /* parameter annotation elements */
// Parameter index in the constructor/method declaration
@index
)
The following two sections demonstrate the usage of these elements.
To demonstrate XML definition for class annotations, consider the following class : org/jvnet/annox/demos/guide/DemoClass.java
package org.jvnet.annox.demos.guide;
public class DemoClass {
public int value = 0;
public DemoClass() {}
public DemoClass(int value) {
this.value = value;
}
public int getValue() {
return this.value;
}
public void setValue() {
this.value = 0;
}
public void setValue(int value) {
this.value = value;
}
public void setValue(String s) {
this.value = Integer.valueOf(s);
}
public void setValue(String s, int radix) {
this.value = Integer.valueOf(s, radix);
}
public int add(int value) {
return this.value += value;
}
}
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 : org/jvnet/annox/demos/guide/Comment.java
package org.jvnet.annox.demos.guide;
public @interface Comment {
public String lang() default "en";
public String value();
}
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 :
<class
xmlns="http://annox.dev.java.net"
xmlns:g="http://annox.dev.java.net/org.jvnet.annox.demos.guide">
<g:Comment>
<value>Annotation for the DemoClass class.</value>
</g:Comment>
<field name="value">
<g:Comment>
<value>Annotation for the value field.</value>
</g:Comment>
</field>
<constructor>
<g:Comment>
<value>Annotation for the default constructor. Since
default constructor does not have any arguments,
we don't need to define any arguments here.</value>
</g:Comment>
</constructor>
<constructor arguments="int">
<g:Comment>
<value>Annotation for the second constructor. To distinguish it
from the default constructor, we have to define constructor
arguments.</value>
</g:Comment>
<parameter index="0">
<g:Comment lang="de">
<value>Annotation for the first parameter of the
constructor.</value>
</g:Comment>
</parameter>
</constructor>
<method name="getValue">
<g:Comment value="Annotation for the getValue() method."/>
</method>
<method name="setValue">
<g:Comment value="Annotation for the setValue() method."/>
</method>
<method name="setValue" arguments="int">
<g:Comment value="Annotation for the setValue(int value) method."/>
</method>
<method name="setValue" arguments="java.lang.String">
<g:Comment value="Annotation for the
setValue(java.lang.String value) method."/>
</method>
<method name="setValue" arguments="java.lang.String, int">
<g:Comment>
<value>Annotation for the
setValue(java.lang.String value, int radix) method.</value>
</g:Comment>
<parameter index="1">
<g:Comment>
<value>Annotation for the radix parameter.</value>
</g:Comment>
</parameter>
</method>
<method name="add">
<g:Comment>
<value>Annotation for the add(int value) method.
Since there is no other add(...) method in the class
("add" is an unambiguous method name),
we don't need to define the arguments attribute.</value>
</g:Comment>
</method>
</class>
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
xmlns="http://annox.dev.java.net"
xmlns:g="http://annox.dev.java.net/org.jvnet.annox.demos.guide">
<g:Comment>
<value>Annotation for the org.jvnet.annox.demos.guide package.</value>
</g:Comment>
</package>
Package definitions may also contain definitions for classes :
<package
xmlns="http://annox.dev.java.net"
xmlns:g="http://annox.dev.java.net/org.jvnet.annox.demos.guide">
<g:Comment>
<value>Annotation for the org.jvnet.annox.demos.guide package.</value>
</g:Comment>
<class name="DemoClass">
<g:Comment>
<value>Annotation for the DemoClass class.</value>
</g:Comment>
<field name="value">
<g:Comment>
<value>Annotation for the value field.</value>
</g:Comment>
</field>
<!-- And so on -->
</class>
</package>
Class resources (such as
DemoClass.ann.xml
) have higher priority than definitions in package resources (such aspackage-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.
- Home
- Migration guide
-
JAXB Maven Plugin
- Quick Start
-
User Guide
- Basic Usage
- Specifying What To Compile
- Referencing Resources in Maven Artifacts
- Using Catalogs
- Using Episodes
- Modular Schema Compilation
- Controlling the Output
- Using JAXB Plugins
- Using a Specific JAXB Version
- Configuring Extension, Validation and XML Security
- IDE Integration
- Miscellaneous
- Configuring Proxies
- Maven Documentation
- Configuration Cheat Sheet
- Common Pitfalls and Problems
-
JAXB2 Basics Plugins
- Using JAXB2 Basics Plugins
- JSR-305 Support
-
JAXB2 Basics Plugins List
- SimpleEquals Plugin
- SimpleHashCode Plugin
- Equals Plugin
- HashCode Plugin
- ToString Plugin
- Copyable Plugin
- Mergeable Plugin
- Inheritance Plugin
- AutoInheritance Plugin
- Wildcard Plugin
- Setters Plugin
- Simplify Plugin
- EnumValue Plugin
- JAXBIndex Plugin
- FixJAXB1058 Plugin
- Commons Lang Plugin
- Default Value Plugin
- Fluent API Plugin
- Namespace Prefix Plugin
- Value Constructor Plugin
- Boolean Getter Plugin
- CamelCase Plugin
- XML ElementWrapper Plugin
- Parent Pointer Plugin
- Property Listener Injector Plugin
- Annox
- JAXB Annotate Plugin
-
HyperJAXB3
- Build System Support
- Customization Guide
- Databases
- Development guide
- Extension guide
- FAQ
- IDE Support
- Java Persistence
- JAXB
- JDK Support
- Project Templates
-
Reference
- Adding vendor-specific annotations
- Features
- Integrating Hyperjaxb3 in builds
- Introduction
- Making schema-derived classes ready for JPA
- Adding required properties
- Applying workarounds for JAXB vs. JPA conflicts
- Enforcing top-level classes
- Generating equals and hashCode methods
- Generating ORM metadata
- Generating persistence unit descriptor
- JPA 2 Support
- Making classes serializable
- Testing generated mappings
- Reference - single page
- Related Projects
- Sample projects
- Solutions
- Target Scenarios
- Test Projects
- Tutorials
- Best Practices
- FAQ
- Sample Projects
- Support
- License
- Distribution