Java Language Compiler le traitement du temps à l'aide du processeur d'annotations


Exemple

Cet exemple montre comment effectuer la vérification du temps de compilation d'un élément annoté.

L'annotation

L'annotation @Setter est un marqueur pouvant être appliqué aux méthodes. L'annotation sera supprimée lors de la compilation et ne sera plus disponible par la suite.

package annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.METHOD)
public @interface Setter {
}

Le processeur d'annotation

La classe SetterProcessor est utilisée par le compilateur pour traiter les annotations. Il vérifie si les méthodes annotées avec l'annotation @Setter sont public , static méthodes non static avec un nom commençant par set et ayant une lettre majuscule comme 4ème lettre. Si l'une de ces conditions n'est pas remplie, une erreur est écrite dans le Messager . Le compilateur écrit ceci dans stderr, mais d'autres outils pourraient utiliser ces informations différemment. Par exemple, l'EDI NetBeans permet à l'utilisateur de spécifier des processeurs d'annotation utilisés pour afficher les messages d'erreur dans l'éditeur.

package annotation.processor;

import annotation.Setter;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic;

@SupportedAnnotationTypes({"annotation.Setter"})
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class SetterProcessor extends AbstractProcessor {

    private Messager messager;

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        // get elements annotated with the @Setter annotation
        Set<? extends Element> annotatedElements = roundEnv.getElementsAnnotatedWith(Setter.class);

        for (Element element : annotatedElements) {
            if (element.getKind() == ElementKind.METHOD) {
                // only handle methods as targets
                checkMethod((ExecutableElement) element);
            }
        }

        // don't claim annotations to allow other processors to process them
        return false;
    }

    private void checkMethod(ExecutableElement method) {
        // check for valid name
        String name = method.getSimpleName().toString();
        if (!name.startsWith("set")) {
            printError(method, "setter name must start with \"set\"");
        } else if (name.length() == 3) {
            printError(method, "the method name must contain more than just \"set\"");
        } else if (Character.isLowerCase(name.charAt(3))) {
            if (method.getParameters().size() != 1) {
                printError(method, "character following \"set\" must be upper case");
            }
        }

        // check, if setter is public
        if (!method.getModifiers().contains(Modifier.PUBLIC)) {
            printError(method, "setter must be public");
        }

        // check, if method is static
        if (method.getModifiers().contains(Modifier.STATIC)) {
            printError(method, "setter must not be static");
        }
    }

    private void printError(Element element, String message) {
        messager.printMessage(Diagnostic.Kind.ERROR, message, element);
    }

    @Override
    public void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);

        // get messager for printing errors
        messager = processingEnvironment.getMessager();
    }

}

Emballage

Pour être appliqué par le compilateur, le processeur d'annotation doit être mis à la disposition du SPI (voir ServiceLoader ).

Pour ce faire, un fichier texte META-INF/services/javax.annotation.processing.Processor doit être ajouté au fichier jar contenant le processeur d’annotation et l’annotation en plus des autres fichiers. Le fichier doit inclure le nom complet du processeur d’annotation, c’est-à-dire qu’il doit ressembler à ceci:

annotation.processor.SetterProcessor

Nous supposerons que le fichier jar s'appelle AnnotationProcessor.jar ci-dessous.

Exemple de classe annotée

La classe suivante est un exemple de classe dans le package par défaut, les annotations étant appliquées aux éléments corrects conformément à la stratégie de rétention. Cependant, seul le processeur d'annotation considère uniquement la seconde méthode comme une cible d'annotation valide.

import annotation.Setter;

public class AnnotationProcessorTest {
    
    @Setter
    private void setValue(String value) {}

    @Setter
    public void setString(String value) {}
    
    @Setter
    public static void main(String[] args) {}
    
}

Utilisation du processeur d'annotations avec javac

Si le processeur d'annotations est détecté à l'aide du SPI, il est automatiquement utilisé pour traiter les éléments annotés. Par exemple, compiler la classe AnnotationProcessorTest utilisant

javac -cp AnnotationProcessor.jar AnnotationProcessorTest.java

donne la sortie suivante

AnnotationProcessorTest.java:6: error: setter must be public
    private void setValue(String value) {}
                 ^
AnnotationProcessorTest.java:12: error: setter name must start with "set"
    public static void main(String[] args) {}
                       ^
2 errors

au lieu de compiler normalement. Aucun fichier .class n'est créé.

Cela pourrait être évité en spécifiant l'option -proc:none pour javac . Vous pouvez également renoncer à la compilation habituelle en spécifiant -proc:only place.

Intégration IDE

Netbeans

Les processeurs d'annotation peuvent être utilisés dans l'éditeur NetBeans. Pour ce faire, le processeur d'annotations doit être spécifié dans les paramètres du projet:

  1. allez dans Project Properties > Build > Compiling

  2. ajouter des coches pour Enable Annotation Processing et Enable Annotation Processing in Editor

  3. cliquez sur Add regard de la liste des processeurs d'annotations

  4. Dans la fenêtre qui apparaît, entrez le nom de classe complet du processeur d’annotation et cliquez sur Ok .

Résultat

Fenêtre de l'éditeur avec un message d'erreur personnalisé