JAnnotation

Written by

in

Building a custom metadata processor using Java’s Annotation Processing APIs allows you to inspect source code structures and generate new files or enforce compile-time rules before your code is built. This native framework (comprising javax.annotation.processing and javax.lang.model) eliminates runtime reflection overhead by executing custom logic directly inside the Java compiler (javac). 1. Define Your Custom Metadata (The Annotation)

Before processing metadata, you must define the annotation interface using the @interface keyword. Use @Retention(RetentionPolicy.SOURCE) to dictate that the metadata is only needed during compilation and does not need to persist into the compiled .class bytecode.

package com.example.metadata; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.TYPE) // Applied to classes or interfaces @Retention(RetentionPolicy.SOURCE) // Discarded after compilation public @interface GenerateConfig { String filename() default “config.properties”; String description(); } Use code with caution. 2. Implement the Processor (AbstractProcessor)

Your core processing engine must extend the javax.annotation.processing.AbstractProcessor class. This requires configuring the types of annotations it handles and processing them via a loop of compilation “rounds”.

package com.example.metadata; import javax.annotation.processing.*; import javax.lang.model.SourceVersion; import javax.lang.model.element.Element; import javax.lang.model.element.TypeElement; import javax.tools.Diagnostic; import javax.tools.FileObject; import javax.tools.StandardLocation; import java.io.Writer; import java.util.Set; // Define what annotations this processor targets @SupportedAnnotationTypes(“com.example.metadata.GenerateConfig”) @SupportedSourceVersion(SourceVersion.RELEASE_21) public class ConfigMetadataProcessor extends AbstractProcessor { private Messager messager; private Filer filer; @Override public synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); // Used to output compilation errors/warnings this.messager = processingEnv.getMessager(); // Used to create new source, class, or resource files this.filer = processingEnv.getFiler(); } @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { for (Element element : roundEnv.getElementsAnnotatedWith(GenerateConfig.class)) { // Read metadata properties from the element GenerateConfig config = element.getAnnotation(GenerateConfig.class); String fileName = config.filename(); String desc = config.description(); String className = element.getSimpleName().toString(); try { // Generate a new resource file based on the extracted metadata FileObject resourceFile = filer.createResource( StandardLocation.CLASS_OUTPUT, “”, fileName ); try (Writer writer = resourceFile.openWriter()) { writer.write(“# Generated from ” + className + “ “); writer.write(“description=” + desc + “ “); } // Print a compilation note messager.printMessage(Diagnostic.Kind.NOTE, “Generated metadata file: ” + fileName); } catch (Exception e) { messager.printMessage(Diagnostic.Kind.ERROR, “Failed to generate metadata: ” + e.getMessage()); } } // Return true to claim the annotations so subsequent processors ignore them return true; } } Use code with caution. 3. Register the Processor via Service Loader

The Java compiler discovers processors using Java’s ServiceLoader architecture. For javac to trigger your code, you must register it in a specific configuration directory:

Create a directory named META-INF/services/ inside your project’s resource folder.

Inside it, create a text file named exactly javax.annotation.processing.Processor.

Add the fully qualified name of your processor class as a single line in that file: com.example.metadata.ConfigMetadataProcessor Use code with caution. 4. Configure Your Build Automation Tool

To prevent compilation conflicts, package your project using a multi-module architecture: one module for your custom annotations/processors, and a separate application module that consumes them. Maven Configuration (pom.xml)

In the consuming application, register the processor module inside the Apache Maven Compiler Plugin:

org.apache.maven.plugins maven-compiler-plugin 3.13.0 com.example metadata-processor-jar 1.0.0 Use code with caution. Gradle Configuration (build.gradle)

In Gradle, configure the dependency scope explicitly using the annotationProcessor configuration block:

dependencies { // Dependency for the annotation interfaces implementation project(‘:metadata-annotations’) // Hook the processor into the compilation lifecycle annotationProcessor project(‘:metadata-processor-jar’) } Use code with caution. Core Structural Constraints to Remember

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *