使用带注释的方法时使java编译器发出警告(如@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 ),而不是引用注释成员的实体。

此问题提供了一种使用ASM将其实现为单独构建步骤的技术。 但我想知道我是否可以通过javac和注释处理以更自然的方式完成它?

是的,这可以使用注释处理。

一个复杂因素是标准注释处理器不会进入方法体(它只检查方法声明)。 您需要一个能够检查每行代码的注释处理器。

Checker Framework旨在构建此类注释处理器。 您只需要定义一个回调,给定方法调用并在调用不可接受时发出javac警告。 (在您的情况下,只是方法的声明是否具有@Unsafe注释。)Checker Framework在程序中的每个方法调用上运行该回调。

我想我可以使用@mernst的响应从技术上实现我的目标,所以我很欣赏这个建议。 但是,我发现另一条路线对我来说效果更好,因为我正在研究商业产品而且不能使用Checker Framework(它的GPL许可证与我们的版本不兼容)。

在我的解决方案中,我使用自己的“标准”java注释处理器来构建使用@Unsafe注释的所有方法的列表。

然后,我开发了一个javac插件。 插件API可以轻松找到AST中任何方法的每次调用。 通过使用此问题的一些提示,我能够从MethodInvocationTree AST节点确定类和方法名称。 然后我将这些方法调用与我创建的早期“列表”进行比较,其中包含使用@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() { @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)来检测插件发出的警告/错误等 – 只有那些我从处理器打印的是显示,虽然当我运行’mvn clean compile’(我正在使用Maven)时,行为是预期的。 这是由于我手上的一些问题,还是插件和AbstractProcessors之间的差异/编译过程的阶段,我不知道。

无论如何:

 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 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() { @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) { } } 

使用注释并调用带注释的方法时,它将如下所示: 在此处输入图像描述

需要记住将注释处理器的完全限定类名添加到名为javax.annotation.processing.Processor的META-INF /服务文件中。 这使它可用于ServiceLoader框架。

使用com.sun **导入问题的Maven用户可能会发现AnimeshSharma的答案很有帮助。

我将我的注释+注释处理器保存在一个单独的项目中。 我不得不通过在pom中添加以下内容来禁用注释处理:

     maven-compiler-plugin  -proc:none      

使用注释并使处理器完成其工作很简单:在我的其他项目(方法foo()的屏幕截图来自的项目中)我只是向包含注释和处理器的项目添加了依赖项。

最后应该提到我是AbstractProcessors和TaskListeners的新手。 我这样做,fx,没有概述代码的性能或健壮性。 目标只是“让它工作”并为类似项目提供存根。