Java Annotation Processors – Creating Configurations
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.
Visit our community platform to share your ideas with us, download resources and access our trainings.
Join us nowConclusion
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.
This article is part 2 of the series „Java Annotation Processing“.
Read all articles now: