使用注释处理器编译时间处理

此示例演示如何对带注释的元素进行编译时检查。

注释

@Setter 注释是可以应用于方法的标记。注释将在编译期间被丢弃,之后无法使用。

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

注释处理器

编译器使用 SetterProcessor 类来处理注释。如果使用 @Setter 注释注释的方法是 public,则检查名称以 set 开头且大写字母为第 4 个字母的非 static 方法。如果不满足其中一个条件,则会向 Messager 写入错误。编译器将此写入 stderr,但其他工具可能以不同方式使用此信息。例如,NetBeans IDE 允许用户指定用于在编辑器中显示错误消息的注释处理器。

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

}

打包

要由编译器应用,需要使注释处理器可用于 SPI(请参阅 ServiceLoader )。

为此,除了其他文件之外,还需要将文本文件 META-INF/services/javax.annotation.processing.Processor 添加到包含注释处理器和注释的 jar 文件中。该文件需要包含注释处理器的完全限定名称,即它应该如下所示

annotation.processor.SetterProcessor

我们假设 jar 文件在下面被称为 AnnotationProcessor.jar

示例注释类

以下类是默认包中的示例类,其中注释根据保留策略应用于正确的元素。但是,只有注释处理器才将第二种方法视为有效的注释目标。

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) {}
    
}

使用带有 javac 的注释处理器

如果使用 SPI 发现注释处理器,则会自动使用它来处理带注释的元素。例如,使用编译 AnnotationProcessorTest

javac -cp AnnotationProcessor.jar AnnotationProcessorTest.java

产生以下输出

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

而不是正常编译。没有创建 .class 文件。

这可以通过为 javac 指定 -proc:none 选项来防止。你也可以通过指定 -proc:only 来放弃通常的编译。

IDE 集成

Netbeans

注释处理器可以在 NetBeans 编辑器中使用。为此,需要在项目设置中指定注释处理器:

  1. Project Properties> Build> Compiling

  2. Enable Annotation ProcessingEnable Annotation Processing in Editor 添加复选标记

  3. 单击注释处理器列表旁边的 Add

  4. 在出现的弹出窗口中输入注释处理器的完全限定类名,然后单击 Ok

结果

StackOverflow 文档