使 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()

它在精神上与 非常相似,但我的注释传达了不同的东西,所以我不能直接使用。有没有办法用注释处理器来实现这一点?注释处理器 API 似乎更侧重于应用注释的实体(在我的示例中),而不是引用带注释成员的实体。@Deprecated@DeprecatedFoo.java

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


答案 1

我认为我本可以使用@mernst的回应在技术上实现我的目标,所以我很感激这个建议。但是,我发现另一种更适合我的途径,因为我正在开发商业产品并且无法包含Checker框架(其GPL许可证与我们的GPL许可证不兼容)。

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

然后,我开发了一个javac插件。插件API使您可以轻松找到AST中任何方法的每个调用。通过使用这个问题中的一些提示,我能够从方法调用树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<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

答案 2

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

一个复杂之处在于,标准注释处理器不会下降到方法主体(它只检查方法声明)。您需要一个检查每行代码的注释处理器。

Checker 框架旨在构建此类注释处理器。你只需要定义一个回调,给定一个方法调用,如果调用不可接受,就会发出javac警告。(在你的例子中,它只是方法的声明是否具有注释。Checker 框架在程序中的每个方法调用上运行该回调。@Unsafe


推荐