使 java 编译器在使用带注释的方法时发出警告(如@deprecated)

假设我定义了一个名为 @Unsafe 的自定义注解.

Let's say I define a custom annotation called @Unsafe.

我想提供一个注释处理器,它将检测 对使用 @Unsafe 注释的方法的引用 并打印警告.

I'd like to provide an annotation processor which will detect references to methods annotated with @Unsafe and print a warning.

例如,给定这段代码...

For example, given this code ...

public class Foo {
  @Unsafe
  public void doSomething() { ... }
}

public class Bar {
  public static void main(String[] args) {
    new Foo().doSomething();
  }
}

...我希望编译器打印如下内容:

... I want the compiler to print something like:

WARN > Bar.java, line 3 : Call to Unsafe API - Foo.doSomething()

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

It is very similar in spirit to @Deprecated, but my annotation is communicating something different, so I can't use @Deprecated directly. Is there a way to achieve this with an annotation processor? The annotation processor API seems to be more focused on the entities applying the annotations (Foo.java in my example) than entities which reference annotated members.

这个question 提供了一种使用 ASM 将其作为单独的构建步骤来实现的技术.但我想知道我是否可以用 javac & 以更自然的方式做到这一点注释处理?

This question provides a technique to achieve it as a separate build step using ASM. But I'm wondering if I can do it in a more natural way with javac & annotation processing?

推荐答案

我想我可以使用@mernst 的回复在技术上实现我的目标,所以我很欣赏这个建议.但是,我发现了另一条更适合我的路线,因为我正在开发商业产品,并且无法合并 Checker 框架(它的 GPL 许可证与我们的不兼容).

I think I could have technically achieved my goal using the response from @mernst, so I appreciate the suggestion. However, I found another route that worked better for me as I'm working on a commercial product and cannot incoporate the Checker Framework (its GPL license is incompatible with ours).

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

In my solution, I use my own "standard" java annotation processor to build a listing of all the methods annotated with @Unsafe.

然后,我开发了一个 javac 插件.插件 API 可以轻松找到 AST 中任何方法的每次调用.通过使用 this question,我能够从 MethodInvocationTree AST 节点确定类和方法名称.然后我将这些方法调用与我之前创建的包含用 @Unsafe 注释的方法的列表"进行比较,并在需要时发出警告.

Then, I developed a javac plugin. The Plugin API makes it easy to find every invocation of any method in the AST. By using some tips from this question, I was able to determine the class and method name from the MethodInvocationTree AST node. Then I compare those method invocations with the earlier "listing" I created containing methods annotated with @Unsafe and issue warnings where required.

这是我的 javac 插件的缩写版本.

Here is an abbreviated version of my javac Plugin.

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 插件,您必须在命令行中提供参数:

Note - in order for the javac plugin to be invoked, you must provide arguments on the command line:

javac -processorpath build/unsafe-plugin.jar -Xplugin:UnsafePlugin

此外,您必须在 unsafe-plugin.jar 中有一个文件 META-INF/services/com.sun.source.util.Plugin,其中包含插件的完全限定名称:

Also, you must have a file META-INF/services/com.sun.source.util.Plugin in unsafe-plugin.jar containing the fully qualified name of the plugin:

com.unsafetest.javac.UnsafePlugin

相关文章