使 java 编译器在使用带注释的方法时发出警告(如@deprecated)
Make the java compiler warn when an annotated method is used (like @deprecated)
假设我定义了一个名为 @Unsafe
的自定义注释。
我想提供一个注释处理器,它将检测 引用 对使用 @Unsafe
注释的方法并打印警告。
例如,给定此代码...
public class Foo {
@Unsafe
public void doSomething() { ... }
}
public class Bar {
public static void main(String[] args) {
new Foo().doSomething();
}
}
...我希望编译器打印如下内容:
WARN > Bar.java, line 3 : Call to Unsafe API - Foo.doSomething()
它在本质上与 @Deprecated
非常相似,但我的注释传达的是不同的东西,所以我不能直接使用 @Deprecated
。有没有办法通过注释处理器实现这一点?注释处理器 API 似乎更关注 应用 注释的实体(在我的示例中为 Foo.java
),而不是 引用 注释成员。
This question 提供了一种使用 ASM 将其作为单独构建步骤实现的技术。但是我想知道我是否可以使用 javac 和注释处理以更自然的方式来完成它?
是的,这可以使用注释处理。
一个复杂的问题是标准注释处理器不会进入方法体(它只检查方法声明)。您需要一个注释处理器来检查每一行代码。
Checker Framework 旨在构建此类注释处理器。您只需要定义一个回调,给定一个方法调用并在调用不可接受时发出 javac 警告。 (在你的例子中,它只是方法的声明是否有一个 @Unsafe
注释。)检查器框架在程序中的每个方法调用上运行该回调。
我认为我可以使用 @mernst 的回复在技术上实现我的目标,所以我很感谢这个建议。但是,我发现了另一种更适合我的方法,因为我正在开发商业产品并且无法合并 Checker Framework(它的 GPL 许可证与我们的许可证不兼容)。
在我的解决方案中,我使用我自己的 "standard" java 注释处理器来构建所有用 @Unsafe
注释的方法的列表。
然后,我开发了一个javac插件。插件 API 可以很容易地找到 AST 中任何方法的每次调用。通过使用 this question 中的一些提示,我能够从 MethodInvocationTree AST 节点确定 class 和方法名称。然后,我将这些方法调用与我之前创建的 "listing" 进行比较,其中包含用 @Unsafe
注释的方法,并在需要时发出警告。
这是我的 javac 插件的简化版本。
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.util.JavacTask;
import com.sun.source.util.Plugin;
import com.sun.source.util.TaskEvent;
import com.sun.source.util.TaskEvent.Kind;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.TreeInfo;
import com.sun.source.util.TaskListener;
import com.sun.source.util.TreeScanner;
public class UnsafePlugin implements Plugin, TaskListener {
@Override
public String getName() {
return "UnsafePlugin";
}
@Override
public void init(JavacTask task, String... args) {
task.addTaskListener(this);
}
@Override
public void finished(TaskEvent taskEvt) {
if (taskEvt.getKind() == Kind.ANALYZE) {
taskEvt.getCompilationUnit().accept(new TreeScanner<Void, Void>() {
@Override
public Void visitMethodInvocation(MethodInvocationTree methodInv, Void v) {
Element method = TreeInfo.symbol((JCTree) methodInv.getMethodSelect());
TypeElement invokedClass = (TypeElement) method.getEnclosingElement();
String className = invokedClass.toString();
String methodName = methodInv.getMethodSelect().toString().replaceAll(".*\.", "");
System.out.println("Method Invocation: " + className + " : " + methodName);
return super.visitMethodInvocation(methodInv, v);
}
}, null);
}
}
@Override
public void started(TaskEvent taskEvt) {
}
}
注意 - 为了调用 javac 插件,您必须在命令行中提供参数:
javac -processorpath build/unsafe-plugin.jar -Xplugin:UnsafePlugin
此外,您必须在 unsafe-plugin.jar 中有一个文件 META-INF/services/com.sun.source.util.Plugin
,其中包含插件的完全限定名称:
com.unsafetest.javac.UnsafePlugin
下面的 AbstractProcessor 处理 greghmerrill 的 @Unsafe 注释,并在调用 @Unsafe 注释的方法时发出警告。
这是对 greghmerrills 自己的答案的轻微修改,这很棒,但是我在让我的 IDE 增量编译器(我使用的是 Netbeans)检测插件发出的 warnings/errors 等时遇到了一些问题 - 仅显示了我从处理器打印的那些,尽管当我 运行 'mvn clean compile' (我正在使用 Maven)时,行为符合预期。这是我手上的问题,还是插件和编译过程的 AbstractProcessors/the 阶段之间的差异,我不知道。
无论如何:
package com.hervian.annotationutils.target;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.util.*;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.TreeInfo;
import java.util.Set;
import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.*;
import javax.tools.Diagnostic;
@SupportedAnnotationTypes({"com.hervian.annotationutils.target.Unsafe"})
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class UnsafeAnnotationProcessor extends AbstractProcessor implements TaskListener {
Trees trees;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
trees = Trees.instance(processingEnv);
JavacTask.instance(processingEnv).setTaskListener(this);
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
//Process @Unsafe annotated methods if needed
return true;
}
@Override public void finished(TaskEvent taskEvt) {
if (taskEvt.getKind() == TaskEvent.Kind.ANALYZE) {
taskEvt.getCompilationUnit().accept(new TreeScanner<Void, Void>() {
@Override
public Void visitMethodInvocation(MethodInvocationTree methodInv, Void v) {
Element method = TreeInfo.symbol((JCTree) methodInv.getMethodSelect());
Unsafe unsafe = method.getAnnotation(Unsafe.class);
if (unsafe != null) {
JCTree jcTree = (JCTree) methodInv.getMethodSelect();
trees.printMessage(Diagnostic.Kind.WARNING, "Call to unsafe method.", jcTree, taskEvt.getCompilationUnit());
}
return super.visitMethodInvocation(methodInv, v);
}
}, null);
}
}
@Override public void started(TaskEvent taskEvt) { } }
当使用注解并调用注解方法时,它将如下所示:
需要记住将注释处理器的完全限定 class 名称添加到名为 javax.annotation.processing.Processor 的 META-INF/service 文件中。这使其可用于 ServiceLoader 框架。
在 com.sun** 导入方面遇到问题的 Maven 用户可能会发现来自 AnimeshSharma 的 this answer 很有帮助。
我将注释 + 注释处理器放在一个单独的项目中。我不得不通过将以下内容添加到 pom 来禁用注释处理:
<build>
<pluginManagement>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<compilerArgument>-proc:none</compilerArgument>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
使用注释并让处理器完成它的工作很简单:在我的另一个项目(方法 foo() 的屏幕截图来自的那个项目)中,我只是向包含注释和处理器的项目添加了一个依赖项。
最后应该提到的是,我是 AbstractProcessors 和 TaskListeners 的新手。我确实,FX,没有代码的性能或健壮性的概述。目标只是 "get it to work" 并为类似项目提供存根。
假设我定义了一个名为 @Unsafe
的自定义注释。
我想提供一个注释处理器,它将检测 引用 对使用 @Unsafe
注释的方法并打印警告。
例如,给定此代码...
public class Foo {
@Unsafe
public void doSomething() { ... }
}
public class Bar {
public static void main(String[] args) {
new Foo().doSomething();
}
}
...我希望编译器打印如下内容:
WARN > Bar.java, line 3 : Call to Unsafe API - Foo.doSomething()
它在本质上与 @Deprecated
非常相似,但我的注释传达的是不同的东西,所以我不能直接使用 @Deprecated
。有没有办法通过注释处理器实现这一点?注释处理器 API 似乎更关注 应用 注释的实体(在我的示例中为 Foo.java
),而不是 引用 注释成员。
This question 提供了一种使用 ASM 将其作为单独构建步骤实现的技术。但是我想知道我是否可以使用 javac 和注释处理以更自然的方式来完成它?
是的,这可以使用注释处理。
一个复杂的问题是标准注释处理器不会进入方法体(它只检查方法声明)。您需要一个注释处理器来检查每一行代码。
Checker Framework 旨在构建此类注释处理器。您只需要定义一个回调,给定一个方法调用并在调用不可接受时发出 javac 警告。 (在你的例子中,它只是方法的声明是否有一个 @Unsafe
注释。)检查器框架在程序中的每个方法调用上运行该回调。
我认为我可以使用 @mernst 的回复在技术上实现我的目标,所以我很感谢这个建议。但是,我发现了另一种更适合我的方法,因为我正在开发商业产品并且无法合并 Checker Framework(它的 GPL 许可证与我们的许可证不兼容)。
在我的解决方案中,我使用我自己的 "standard" java 注释处理器来构建所有用 @Unsafe
注释的方法的列表。
然后,我开发了一个javac插件。插件 API 可以很容易地找到 AST 中任何方法的每次调用。通过使用 this question 中的一些提示,我能够从 MethodInvocationTree AST 节点确定 class 和方法名称。然后,我将这些方法调用与我之前创建的 "listing" 进行比较,其中包含用 @Unsafe
注释的方法,并在需要时发出警告。
这是我的 javac 插件的简化版本。
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.util.JavacTask;
import com.sun.source.util.Plugin;
import com.sun.source.util.TaskEvent;
import com.sun.source.util.TaskEvent.Kind;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.TreeInfo;
import com.sun.source.util.TaskListener;
import com.sun.source.util.TreeScanner;
public class UnsafePlugin implements Plugin, TaskListener {
@Override
public String getName() {
return "UnsafePlugin";
}
@Override
public void init(JavacTask task, String... args) {
task.addTaskListener(this);
}
@Override
public void finished(TaskEvent taskEvt) {
if (taskEvt.getKind() == Kind.ANALYZE) {
taskEvt.getCompilationUnit().accept(new TreeScanner<Void, Void>() {
@Override
public Void visitMethodInvocation(MethodInvocationTree methodInv, Void v) {
Element method = TreeInfo.symbol((JCTree) methodInv.getMethodSelect());
TypeElement invokedClass = (TypeElement) method.getEnclosingElement();
String className = invokedClass.toString();
String methodName = methodInv.getMethodSelect().toString().replaceAll(".*\.", "");
System.out.println("Method Invocation: " + className + " : " + methodName);
return super.visitMethodInvocation(methodInv, v);
}
}, null);
}
}
@Override
public void started(TaskEvent taskEvt) {
}
}
注意 - 为了调用 javac 插件,您必须在命令行中提供参数:
javac -processorpath build/unsafe-plugin.jar -Xplugin:UnsafePlugin
此外,您必须在 unsafe-plugin.jar 中有一个文件 META-INF/services/com.sun.source.util.Plugin
,其中包含插件的完全限定名称:
com.unsafetest.javac.UnsafePlugin
下面的 AbstractProcessor 处理 greghmerrill 的 @Unsafe 注释,并在调用 @Unsafe 注释的方法时发出警告。
这是对 greghmerrills 自己的答案的轻微修改,这很棒,但是我在让我的 IDE 增量编译器(我使用的是 Netbeans)检测插件发出的 warnings/errors 等时遇到了一些问题 - 仅显示了我从处理器打印的那些,尽管当我 运行 'mvn clean compile' (我正在使用 Maven)时,行为符合预期。这是我手上的问题,还是插件和编译过程的 AbstractProcessors/the 阶段之间的差异,我不知道。
无论如何:
package com.hervian.annotationutils.target;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.util.*;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.TreeInfo;
import java.util.Set;
import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.*;
import javax.tools.Diagnostic;
@SupportedAnnotationTypes({"com.hervian.annotationutils.target.Unsafe"})
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class UnsafeAnnotationProcessor extends AbstractProcessor implements TaskListener {
Trees trees;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
trees = Trees.instance(processingEnv);
JavacTask.instance(processingEnv).setTaskListener(this);
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
//Process @Unsafe annotated methods if needed
return true;
}
@Override public void finished(TaskEvent taskEvt) {
if (taskEvt.getKind() == TaskEvent.Kind.ANALYZE) {
taskEvt.getCompilationUnit().accept(new TreeScanner<Void, Void>() {
@Override
public Void visitMethodInvocation(MethodInvocationTree methodInv, Void v) {
Element method = TreeInfo.symbol((JCTree) methodInv.getMethodSelect());
Unsafe unsafe = method.getAnnotation(Unsafe.class);
if (unsafe != null) {
JCTree jcTree = (JCTree) methodInv.getMethodSelect();
trees.printMessage(Diagnostic.Kind.WARNING, "Call to unsafe method.", jcTree, taskEvt.getCompilationUnit());
}
return super.visitMethodInvocation(methodInv, v);
}
}, null);
}
}
@Override public void started(TaskEvent taskEvt) { } }
当使用注解并调用注解方法时,它将如下所示:
需要记住将注释处理器的完全限定 class 名称添加到名为 javax.annotation.processing.Processor 的 META-INF/service 文件中。这使其可用于 ServiceLoader 框架。
在 com.sun** 导入方面遇到问题的 Maven 用户可能会发现来自 AnimeshSharma 的 this answer 很有帮助。
我将注释 + 注释处理器放在一个单独的项目中。我不得不通过将以下内容添加到 pom 来禁用注释处理:
<build>
<pluginManagement>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<compilerArgument>-proc:none</compilerArgument>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
使用注释并让处理器完成它的工作很简单:在我的另一个项目(方法 foo() 的屏幕截图来自的那个项目)中,我只是向包含注释和处理器的项目添加了一个依赖项。
最后应该提到的是,我是 AbstractProcessors 和 TaskListeners 的新手。我确实,FX,没有代码的性能或健壮性的概述。目标只是 "get it to work" 并为类似项目提供存根。