如何通过 AnnotationProcessor 访问 TypeUse 注解
How to access TypeUse annotation via AnnotationProcessor
问题:
- 是否可以通过注释处理器访问带有
@Target(ElementType.TYPE_USE)
注释的元素?
- 是否可以通过注释处理器访问注释类型边界?
非常感谢我错过的相关文档链接。
上下文:
注解:
@Target(ElementType.TYPE_USE)
@Retention(RetentionPolicy.SOURCE)
public @interface TypeUseAnno {}
一个例子class:
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;
}
}
在 class 路径上使用 Processor
编译上述 SomeClass
将显示 "Intialized"
消息,但永远不会调用 process(...)
方法。
当注释出现在方法参数上时,使用 @Target(ElementType.PARAMETER)
向处理器添加另一个注释效果很好。如果方法参数用 @TypeUseAnno
注释,过程将再次忽略该元素。
TYPE_USE
注释有点棘手,因为编译器对待它们的方式与 "old usage" 注释不同。
因此,正如您正确观察到的那样,它们不会传递给注释处理器,并且您的 process()
方法永远不会接收到它们。
那么在编译时如何使用它们呢?
在 Java 8 中,在引入这些注释的同时,还引入了附加到 java 编译的新方法。您现在可以将侦听器附加到编译任务,并触发您自己的源代码遍历。因此,您访问注释的任务分为两部分。
- 挂钩编译器
- 实施您的分析器
广告 1。
在 Java 8 中有 2 个选项可以挂接编译器
1. 使用新的编译器插件 API (https://docs.oracle.com/javase/8/docs/jdk/api/javac/tree/com/sun/source/util/Plugin.html)
2. 使用注解处理器
我没有经常使用选项 #1,因为它需要明确指定为 javac 参数。所以我将描述选项 #1:
您必须将 TaskListener
附加到正确的编译阶段。有不同的阶段。以下是唯一的一个,在此期间您可以访问表示完整源代码的语法树,包括方法体(请记住,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 代码生成,因为编译器不会选择在此阶段创建的文件进行进一步编译。
问题:
- 是否可以通过注释处理器访问带有
@Target(ElementType.TYPE_USE)
注释的元素? - 是否可以通过注释处理器访问注释类型边界?
非常感谢我错过的相关文档链接。
上下文:
注解:
@Target(ElementType.TYPE_USE)
@Retention(RetentionPolicy.SOURCE)
public @interface TypeUseAnno {}
一个例子class:
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;
}
}
在 class 路径上使用 Processor
编译上述 SomeClass
将显示 "Intialized"
消息,但永远不会调用 process(...)
方法。
当注释出现在方法参数上时,使用 @Target(ElementType.PARAMETER)
向处理器添加另一个注释效果很好。如果方法参数用 @TypeUseAnno
注释,过程将再次忽略该元素。
TYPE_USE
注释有点棘手,因为编译器对待它们的方式与 "old usage" 注释不同。
因此,正如您正确观察到的那样,它们不会传递给注释处理器,并且您的 process()
方法永远不会接收到它们。
那么在编译时如何使用它们呢?
在 Java 8 中,在引入这些注释的同时,还引入了附加到 java 编译的新方法。您现在可以将侦听器附加到编译任务,并触发您自己的源代码遍历。因此,您访问注释的任务分为两部分。
- 挂钩编译器
- 实施您的分析器
广告 1。 在 Java 8 中有 2 个选项可以挂接编译器 1. 使用新的编译器插件 API (https://docs.oracle.com/javase/8/docs/jdk/api/javac/tree/com/sun/source/util/Plugin.html) 2. 使用注解处理器
我没有经常使用选项 #1,因为它需要明确指定为 javac 参数。所以我将描述选项 #1:
您必须将 TaskListener
附加到正确的编译阶段。有不同的阶段。以下是唯一的一个,在此期间您可以访问表示完整源代码的语法树,包括方法体(请记住,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 代码生成,因为编译器不会选择在此阶段创建的文件进行进一步编译。