如何通过AnnotationProcessor访问TypeUse注释

问题描述 投票:10回答:1

Question:

  1. 是否可以通过注释处理器访问使用@Target(ElementType.TYPE_USE)注释注释的元素?
  2. 是否可以通过注释处理器访问带注释的类型边界?

我错过的相关文档的链接非常感谢。

Context:

注释:

@Target(ElementType.TYPE_USE)
@Retention(RetentionPolicy.SOURCE)
public @interface TypeUseAnno {}

一个示例类:

public class SomeClass extends HashMap<@TypeUseAnno String, String> {}

处理器:

@SupportedSourceVersion(SourceVersion.RELEASE_8)
@SupportedAnnotationTypes("base.annotations.TypeUseAnno")
public class Processor extends AbstractProcessor {

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        this.processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING, "Initialized.");
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        this.processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING, "Invoked.");
        for (TypeElement annotation : annotations) {
            this.processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING, "" + roundEnv.getElementsAnnotatedWith(annotation));
        }
        return true;
    }
}

在类路径上使用SomeClass编译上面的Processor将显示"Intialized"消息,但从不调用process(...)方法。当注释出现在方法参数上时,使用@Target(ElementType.PARAMETER)向处理器添加另一个注释可以正常工作。如果使用@TypeUseAnno注释方法参数,则进程将再次忽略该元素。

java annotation-processing
1个回答
5
投票

TYPE_USE注释有点棘手,因为编译器对它们的处理方式与“旧用法”注释不同。

因此,正如您正确观察到的那样,它们不会传递给注释处理器,而您的process()方法永远不会收到它们。

那么如何在编译时使用它们呢?

在Java 8中,引入了这些注释,还引入了附加到java编译的新方法。您现在可以将侦听器附加到编译任务,并触发您自己遍历的源代码。因此,访问注释的任务分为两部分。

  1. 钩到编译器
  2. 实施您的分析仪

广告1.在Java 8中有两个选项可以挂钩编译器1.使用新的编译器插件API(https://docs.oracle.com/javase/8/docs/jdk/api/javac/tree/com/sun/source/util/Plugin.html)2。使用注释处理器

我没有使用#1选项,因为它需要明确指定为javac参数。所以我将描述选项#1:

你必须将TaskListener附加到propper编译阶段。有各个阶段。下面是唯一一个,在此期间您可以访问语法树,表示完整的源代码,包括方法体(请记住,TYPE_USE注释甚至可以用于局部变量声明。

@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class EndProcessor extends AbstractProcessor {

    @Override
    public synchronized void init(ProcessingEnvironment env) {
        super.init(env);
        Trees trees = Trees.instance(env);
        JavacTask.instance(env).addTaskListener(new TaskListener() {

            @Override
            public void started(TaskEvent taskEvent) {
                // Nothing to do on task started event.
            }

            @Override
            public void finished(TaskEvent taskEvent) {
                if(taskEvent.getKind() == ANALYZE) {
                    new MyTreeScanner(trees).scan(taskEvent.getCompilationUnit(), null);
                }
            }

        });
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        // We don't care about this method, as it will never be invoked for our annotation.
        return false;
    }
}

广告2.现在MyTreeScanner可以扫描完整的源代码,并找到注释。无论你使用Plugin还是AnnotationProcessor方法,这都适用。这仍然很棘手。你必须实现TreeScanner,或者通常扩展TreePathScanner。这表示访问者模式,您必须正确分析哪些元素是您感兴趣的元素。

让我们举一个简单的例子,它可以以某种方式对局部变量声明作出反应(给我5分钟):

class MyTreeScanner extends TreePathScanner<Void, Void> {
    private final Trees trees;

    public MyTreeScanner(Trees trees) {
        this.trees = trees;
    }

    @Override
    public Void visitVariable(VariableTree tree, Void aVoid) {
        super.visitVariable(variableTree, aVoid);
        // This method might be invoked in case of
        //  1. method field definition
        //  2. method parameter
        //  3. local variable declaration
        // Therefore you have to filter out somehow what you don't need.
        if(tree.getKind() == Tree.Kind.VARIABLE) {
            Element variable = trees.getElement(trees.getPath(getCurrentPath().getCompilationUnit(), tree));
            MyUseAnnotation annotation = variable.getAnnotation(MyUseAnnotation.class);
            // Here you have your annotation.
            // You can process it now.
        }
        return aVoid;
    }
}

这是非常简短的介绍。有关实际示例,您可以查看以下项目源代码:https://github.com/c0stra/fluent-api-end-check/tree/master/src/main/java/fluent/api/processors

在开发此类功能的同时进行良好测试也非常重要,因此您可以调试,反向工程并解决您将在此领域遇到的所有棘手问题;)为此,您也可以从中受到启发:https://github.com/c0stra/fluent-api-end-check/blob/master/src/test/java/fluent/api/EndProcessorTest.java

也许我的最后一句话,因为javac真的使用了不同的注释,但是有一些限制。例如。它不适合触发java代码生成,因为编译器不会选择在此阶段创建的文件以进行进一步编译。

© www.soinside.com 2019 - 2024. All rights reserved.