使用註釋處理器編譯時間處理

此示例演示如何對帶註釋的元素進行編譯時檢查。

註釋

@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 文件