如何通过AnnotationProcessor访问TypeUse注释 [英] How to access TypeUse annotation via AnnotationProcessor

查看:53
本文介绍了如何通过AnnotationProcessor访问TypeUse注释的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

高度赞赏我错过的相关文档的链接.

Links to related documentation I missed are highly appreciated.

注释:

@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;
    }
}

使用类路径上的 Processor 编译上述 SomeClass ,将显示"Intialized" 消息,但显示 process(...)方法永远不会被调用.当方法参数上存在注释时,使用 @Target(ElementType.PARAMETER)向处理器添加另一个注释会很好.如果方法参数用 @TypeUseAnno 注释,则过程将再次忽略该元素.

Compiling the above SomeClass with Processor on the classpath will show the "Intialized" message but the process(...) method is never invoked. Adding another annotation to the processor with @Target(ElementType.PARAMETER) works fine when the annotation is present on a method parameter. If the method parameter is annotated with @TypeUseAnno the process will again ignore the element.

推荐答案

TYPE_USE 注释有点棘手,因为编译器对待它们的方式与旧用法"注释不同.

The TYPE_USE annotations are a bit tricky, because the compiler treats them differently, than the "old usage" annotations.

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

So as you correctly observed, they are not passed to annotation processor, and your process() method will never receive them.

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

So how to use them at compilation time?

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

In Java 8, where these annotations got introduced, there was also introduced new way to attach to java compilation. You can now attach listener to compilation tasks, and trigger your own traversal of the source code. So your task to access the annotation splits into two.

  1. 连接到编译器
  2. 安装分析仪

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

Ad 1. There are 2 options to hook on the compiler in Java 8 1. Using new compiler plugin API (https://docs.oracle.com/javase/8/docs/jdk/api/javac/tree/com/sun/source/util/Plugin.html) 2. Using annotation processor

我没有太多使用选项#1,因为它需要明确地指定为javac参数.因此,我将介绍选项#1:

I haven't used option #1 much, because it needs to be explicitely specified as javac parameter. So I'll describe option #1:

您必须将 TaskListener 附加到正确的编译阶段.有多个阶段.下一个是唯一的一个,在此期间,您具有可访问的语法树,该树表示完整的源代码,包括方法主体(请记住,即使在局部变量声明中也可以使用 TYPE_USE 批注.

You have to attach TaskListener to the propper compilation phase. There are various phases. Following one is the only one, during which you have accessible syntax tree representing full source code including method bodies (remember, that TYPE_USE annotations can be used even on local variable declarations.

@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 .这代表了一种访客模式,您必须在其中进行适当的分析,以找出您要访问的哪些元素.

Ad 2. Now the MyTreeScanner can scan the full source code, and find the annotations. That applies no matter if you used the Plugin or AnnotationProcessor approach. This is still tricky. You have to implement the TreeScanner, or typically extend the TreePathScanner. This represents a visitor pattern, where you have to properly analyze, which elements are of your interest to be visited.

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

Let's give simple example, that can somehow react on local variable declaration (give me 5 minutes):

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

This is very brief introduction. For real examples you can have a look at following project source code: 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

It's also very important to have good tests while developing such features, so you can debug, reverse engineer and solve all the tricky issues you'll face in this area ;) For that you can also get inspired here: https://github.com/c0stra/fluent-api-end-check/blob/master/src/test/java/fluent/api/EndProcessorTest.java

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

Maybe my last remark, as the annotations are really used differently by the javac, there are some limitations. E.g. it's not suitable for triggering java code generation, because the compiler doesn't pick files created during this phase for further compilation.

这篇关于如何通过AnnotationProcessor访问TypeUse注释的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆