Skip to content
Laurent Schoelens edited this page Aug 28, 2023 · 1 revision

Introduction

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());

Java annotations as XML elements

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 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):

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.

Laconic syntax for single-element annotations

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

Using the annotated element factory

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 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.

Reading annotated elements from XML resources

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. 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.

Defining class annotations in XML resources

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>

Defining package annotations in XML resources

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 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.

Caching

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.

Clone this wiki locally