Annotation Processing

7 July 2006 11:50 Levi Hoogenberg Java

Dit artikel gaat over Annotation Processing, het verwerken van annotaties. Na een beknopte uitleg over wat annotations zijn geef ik aan welke manieren er zijn om annotaties te verwerken en ga ik wat dieper in op die manieren.

Annotations: een introductie
Annotations zijn in JDK 5 als JSR 175, a metadata facility for the Java™ Programming Language toegevoegd aan de taal. Een annotation werkt op een element, zoals een klasse, een methode of een andere annotation, en verandert niets aan de betekenis van een programma. Hij kan echter wel door tools en bibliotheken worden gebruikt.


Een annotation heeft een retention, die beschrijft hoe lang een annotation zichtbaar blijft. Dit wordt uitgedrukt door de @Retention-annotation. Er zijn drie mogelijke retention policy’s:

  • RetentionPolicy.SOURCE: De annotation is alleen beschikbaar voor de compiler. Voorbeelden hiervan zijn @SuppressWarnings en @Deprecated.
  • RetentionPolicy.CLASS De annotation wordt opgenomen in de classfile, maar is niet runtime uit te vragen. Dit is de standaard als er geen retention policy wordt opgegeven.
  • RetentionPolicy.RUNTIME De annotation kan runtime worden uitgevraagd.

In de volgende twee paragrafen laat ik voor de laatste twee retention policy’s zien hoe annotations kunnen worden verwerkt. Dit doe ik aan de hand van de volgende voorbeeldannotation:

 
    @Retention (RetentionPolicy.RUNTIME)
    @Target (ElementType.METHOD)
    public @interface TeDoen {
        String value ();
    }

Deze annotation geeft voor een methode aan dat er nog iets aan moet gebeuren. Hij zou als volgt kunnen worden gebruikt:

 
    @TeDoen ("documenteren")
    public void doeIngewikkeldeDingen () {
        // ...
    }

Annotations op klasseniveau verwerken

Voor annotations met de retention policy CLASS kan de Annotation Processing Tool (kortweg APT) worden gebruikt. Deze tool is bedoeld om bronbestanden te genereren op basis van annotations en werkt op de volgende manier:

  • Er wordt voor de broncode bepaald welke annotations er zijn;
  • Voor deze annotations worden annotation processor factory’s gezocht;
  • Aan al deze fabrieken wordt verzocht een annotation processor te maken;
  • Deze verwerkers worden gebruikt, waarbij er eventueel nieuwe bronbestanden gegenereerd kunnen worden;
  • De procedure herhaalt zich voor de gegenereerde bronbestanden.

De volgende annotation processor factory levert een annotation processor die alle met @TeDoen annotated methodes opsomt van types die aan APT worden meegegeven:

 
    public class TeDoenVerwerkerfabriek implements AnnotationProcessorFactory {
        public AnnotationProcessor getProcessorFor (Set<AnnotationTypeDeclaration> annotaties,
                AnnotationProcessorEnvironment omgeving) {
            return new TeDoenVerwerker (omgeving);
        }
 
        public Collection<String> supportedAnnotationTypes () {
            return Collections.singleton (TeDoen.class.getName ());
        }
 
        public Collection<String> supportedOptions () {
            return Collections.<String>emptyList ();
        }
 
        private class TeDoenVerwerker implements AnnotationProcessor {
            private AnnotationProcessorEnvironment omgeving;
 
            public TeDoenVerwerker (AnnotationProcessorEnvironment omgeving) {
                this.omgeving = omgeving;
            }
 
            public void process () {
                for (TypeDeclaration type: omgeving.getSpecifiedTypeDeclarations ()) {
                    for (MethodDeclaration methode: type.getMethods ()) {
                        TeDoen teDoen = methode.getAnnotation (TeDoen.class);
 
                        System.out.printf ("%s#%s: %s\n",
                            type.getSimpleName (), methode.getSimpleName (), teDoen.value ());
                    }
                }
            }
        }
    }

De door APT gebruikte API, die stukken uitgebreider en krachtiger is dan het fragment dat ik hier heb laten zien, bevindt zich in de com.sun.mirror-package. Tijdens het compileren van de klasse is tools.jar dus nodig op het classpath.

APT kan worden uitgevoerd met de bij de JDK meegeleverde command line tool apt, maar ook de toolondersteuning neemt toe: De 1.7-bètaversie van Ant bevat een -taak en ook vanuit Eclipse kan vanaf de eerste RC van versie 3.2 APT worden gebruikt.

Annotations via reflection verwerken
Annotations met de retention policy RUNTIME kunnen via reflection worden uitgelezen. Van elk element dat de interface AnnotatedElement implementeert (zoals Class, Field en Package) kan via methodes als isAnnotationPresent en getAnnotations worden bepaald welke annotations er op ingesteld zijn.

Het volgende voorbeeld somt alle methodes van een klasse en wat er nog aan moet gebeuren op:

    public void somOp (Class<?> klasse) {
        for (Method methode: klasse.getMethods ()) {
            TeDoen teDoen = methode.getAnnotation (TeDoen.class);
 
            if (teDoen != null) {
                System.out.printf ("%s: %s\n",
                    methode.getName (), teDoen.value ());
            }
        }
    }

1 reactie »

  1. Leuk artikel!

    Klein detail betreffende de voorbeelden. Ik zie dat je steeds \n gebruikt…? Alhoewel dit meestal wel werkt kun eingelijk beter de ‘%n’ notatie gebruiken; die schrijft een platform specifieke (System.getProperty(“line.separator”)) separator.

    peter maas August 19, 2006 12:04

Reageer

RSS feed for comments on this post · TrackBack URI