Cloudogu Logo

Hallo, wir sind Cloudogu!

Experten für Software-Lifecycle-Management und Prozess­auto­mati­sierung, Förderer von Open-Source-Soft­ware und Entwickler des Cloudogu EcoSystem.

featured image Java Annotation Processors – Konfigurationsdateien generieren
06.08.2018 in Technology

Java Annotation Processors – Konfigurationsdateien generieren


Sebastian Sdorra
Sebastian Sdorra

Softwareentwickler


+++ Der Originalartikel kann hier heruntergeladen werden: Zeitschriftenartikel, veröffentlicht in JAVA PRO 02/2018.+++

Im 1.Teil der Blogartikel-Serie zu Java Annotation Prozessoren haben wir erfahren, wie man einen einfachen Annotation Prozessor schreibt, registriert und nutzt. Nun widmen wir uns der Frage, wie man Konfigurationsdateien generieren kann.

Konfigurationsdateien generieren

Im zweiten Abschnitt wollen wir uns dem Erzeugen von Konfigurationsdateien für eine einfache Plugin Bibliothek widmen. Dafür werden wir einen Annotation Prozessor schreiben, der alle Klassen, die mit einer @Extension versehen wurden, in eine XML-Datei schreibt. Zudem vollständigen Namen der Klasse soll außerdem noch das Javadoc der Klasse mit in die XML-Datei geschrieben werden. Zusätzlich werden wir eine Klasse schreiben die es uns erlaubt diese Dateien aus dem Classpath auszulesen. Es ist auch möglich alle Klassen mit einer @Extension Annotation zu finden, ohne einen Annotation Prozessor zu verwenden. Dafür müsste man aber alle Elemente des Classpath (Ordner und Jar-Dateien) öffnen, jede Klasse laden und mit Reflection überprüfen, ob die Klasse die gesuchte Annotation hat. Dieses Vorgehen ist sehr viel aufwändiger, anfälliger für Fehler und deutlich langsamer.

Die Extension Annotation

@Documented
@Target(ElementType.TYPE)
public @interface Extension {
}

Die Extension Annotation ähnelt sehr der Log-Annotation aus dem ersten Abschnitt, mit Ausnahme der Documented Annotation. @Documented sorgt dafür, dass unsere Annotation im Javadoc der annotierten Klasse auftaucht.

Der Extension Annotation Prozessor

Der ExtensionProcessor sammelt zuerst alle Klassen die mit unserer Extension Annotation versehen wurden in einem Set:

Set<ExtensionDescriptor> descriptors = new LinkedHashSet<>();
for ( TypeElement annotation : annotations ) {
    for ( Element extension : roundEnv.getElementsAnnotatedWith(annotation) ) {
        ExtensionDescriptor descriptor = createDescriptor(extension);
        descriptors.add(descriptor);
    }
}

Die createDescriptor Methode speichert dabei den Namen und das Javadoc der annotierten Klasse in einer eigenen Klasse namens ExtensionDescriptor. Den Namen kann man über den Typ des Elementes erfragen: extension.asType().toString()

Das JavaDoc der Klasse kann man über Elements des ProcessingEnvironments erfragen: processingEnv.getElementUtils().getDocComment(extension).trim()

Nachdem wir alle Extensions gesammelt haben, können wir unsere XML-Datei schreiben. Damit unsere XML-Datei im Classpath verfügbar ist, muss sie in das richtige Verzeichnis geschrieben werden. Das Verzeichnis lässt sich über die Filer Klasse des ProcessingEnvironment herausfinden:

Filer filer = processingEnv.getFiler();
FileObject fileObject = filer.getResource(StandardLocation.CLASS_OUTPUT, "", "extensions.xml");
File extensionsFile = new File(fileObject.toUri());

Jetzt müssen wir die Extensions Datei nur noch mit Inhalt füllen. Dafür erstellen wir eine Wrapper Klasse für unsere ExtensionDescriptor Klasse und annotieren beide mit JAXB Annotationen. Anschließend können wir die Extensions-Datei mit Hilfe von JAXB schreiben: JAXB.marshal(new ExtensionDescriptorWrapper(descriptors), file);

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<spl-extensions>
    <extensions>
        <className>com.cloudogu.blog.AhoiService</className>
        <description>Says ahoi to someone.</description>
    </extensions>
    <extensions>
        <className>com.cloudogu.blog.HelloService</className>
        <description>Says hello to someone.</description>
    </extensions>
</spl-extensions>

Diese Datei sollte sich im selben Verzeichnis wie die kompilierten Klassen befinden (bei Maven target/classes).

Extensions Util

Um die Extensions zur Laufzeit wieder auszulesen, können wir uns leicht eine Hilfsklasse schreiben:

public static List<ExtensionDescriptor> getExtensions() throws IOException {
    List<ExtensionDescriptor> descriptors = new ArrayList<>();
    Enumeration<URL> extensionFiles = Thread.currentThread().getContextClassLoader().getResources(LOCATION);
    while (extensionFiles.hasMoreElements()) {
        URL extensionFile = extensionFiles.nextElement();
        ExtensionDescriptorWrapper extensionDescriptorWrapper = JAXB.unmarshal(extensionFile, ExtensionDescriptorWrapper.class);
        descriptors.addAll(extensionDescriptorWrapper.getExtensions());
    }
    return descriptors;
}

Mit dieser Methode werden alle Extension XML-Dateien im Classpath gefunden. Außerdem werden in einer Liste alle Klassen gespeichert, die mit einer Extension Annotation annotiert wurden. Da wir den ContextClassLoader des Threads verwenden, können sich unsere Extensions XML-Dateien sogar in verschiedenen JAR-Dateien befinden.

Wenn wir jetzt alle Extension Klassen unserer Anwendung ausgeben wollen, können wir folgenden Code verwenden:

for (ExtensionDescriptor descriptor : Extensions.getExtensions()) {
    System.out.println(descriptor);
}

Das gesamte Beispiel kann unter part-2 des GitHub Repositories gefunden werden.

Beispiele aus der Open Source Welt

Ein prominentes Beispiel für einen Annotation Prozessor der Konfigurationsdateien generiert, ist der META-INF/services generator von Kohsuke Kawaguchi, der aus einer MetaInfServices Annotation die Konfiguration für den Java 6 ServiceLoader erzeugen kann.

Ein weiteres Beispiel ist das Plugin Framework von SCM-Manager 2.0.0. SCM-Manager hat in Version 1 noch Classpath Scanning verwendet um die Erweiterungen zu finden. Durch den Umstieg auf Annotation Prozessoren konnte die Startzeit von SCM-Manager 2 drastisch verkürzt werden.

Cloudogu Platform Logo

Besuchen Sie unsere Community-Plattform, um Ihre Ideen zu teilen, Ressourcen herunterzuladen und auf unsere Schulungen zuzugreifen.

Jetzt mitmachen
Cloudogu Platform Logo

Fazit

In diesem zweiten Blogbeitrag haben wir uns auf das Erstellen von Konfigurationsdateien sowie die Erweiterung des Annotationsprozessors konzentriert. Im dritten und letzten Teil zeigen wir, wie Code mit Annotation Prozessoren generiert werden kann.