Cloudogu Logo

Hello, we are Cloudogu!

Experts in Software Lifecycle Management and process auto­mation, supporter of open source soft­ware and developer of the Cloudogu EcoSystem.

featured image Java Annotation Processors – Creating Configurations
08/06/2018 in Technology

Java Annotation Processors – Creating Configurations


Sebastian Sdorra
Sebastian Sdorra

Software Developer


This article is part 2 of the series „Java Annotation Processing“
Read the first part now.

In the first blog post of this series we have learned how to write, register and use a simple Annotation Processor. In this part we will take a closer look at the creation of configurations.

Generating configuration files

In the second section, we would like to focus on generating configuration files for a simple plugin library. To do this, we will write an annotation processor that exports all classes which are annotated with an @Extension annotation to an XML file. In addition to the full name of the class, the Javadoc for the class is also written to the XML file. Additionally, we will write a class that allows us to read these files from the classpath.

It is also possible to find all classes with an @Extension annotation without using an annotation processor. However, to do this, you have to open all elements of the classpath (folders and Jar files), load each class and check with Reflection to see if the class has the annotation you are looking for. This method is a lot more laborious, more prone to errors, and significantly slower.

The Extension annotation

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

The Extension annotation is very similar to the Log annotation from the first section, apart from the Documented annotation. @Documented ensures that our annotation shows up in the Javadoc of the annotated class.

The extension annotation processor

The ExtensionProcessor first compiles all classes that are annotated with our Extension annotation into a set:

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

The createDescriptor method saves the name and the Javadoc of the annotated class in its own class, called ExtensionDescriptor. The name can be queried via the element type: extension.asType().toString()

The JavaDoc of the class can be accessed via the Elements of the ProcessingEnvironment: processingEnv.getElementUtils().getDocComment(extension).trim()

Once we have collected all of the extensions, we can write our XML file. In order for our XML file to be accessible in the classpath, it must be saved to the correct directory. The correct directory can be determined using the Filer class of the ProcessingEnvironment:

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

Now we just need to populate the Extensions file with content. To do this, we create a Wrapper class for our ExtensionDescriptor class and annotate both with JAXB annotations. Then, we can write the Extensions file with the help of JAXB: JAXB.marshal(new ExtensionDescriptorWrapper(descriptors), file);

With the ExtensionProcessor, we now have all we need to save all classes with an Extension annotation in one file during compilation. The result should look something like this:

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

This file should be located in the same directory as the compiled classes (in Maven: target/classes).

Extension Util

To read out the extensions during runtime, a simple helper class can be written:

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;
}

Using this method, all of the Extension XML files in the classpath can be found. Moreover, all classes annotated with an Extension annotation are saved to a list. Since we are using the ContextClassLoader of the thread, it is possible that our Extension XML files are located in different JAR files.

If we now wish to export all of the Extension classes of our application, we can use the following code:

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

The entire example can be found under part-2 of the GitHub repository.

Examples from the open source world

A prominent example of an annotation processor that generates configuration files is the META-INF/services generator from Kohsuke Kawaguchi, which can generate a configuration for the Java 6 ServiceLoader from a MetaInfServices annotation.

Another example is the plugin framework of SCM-Manager 2.0.0. In the first version of SCM-Manager, classpath scanning was still used to find extensions. Switching to annotation processors drastically reduced the boot time for SCM-Manager 2.

Cloudogu Platform Logo

Visit our community platform to share your ideas with us, download resources and access our trainings.

Join us now
Cloudogu Platform Logo

Conclusion

In this second blog post of the series on Java annotation processors we focused on creating configuration files as well as the extension annotation processor. The third and last part we will show how code can be generated with annotation processors.

+++ If you want to read the post in German, you can download the original article, published in JAVA PRO 02/2018. +++

This article is part 2 of the series „Java Annotation Processing“.
Read all articles now: