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

问题描述 投票:0回答:3

假设我定义了一个名为

@Unsafe
的自定义注释。

我想提供一个注释处理器,它将检测用 @Unsafe 注释的方法的

references
并打印警告。

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

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 和注释处理以更自然的方式做到这一点?

java annotations deprecated
3个回答
5
投票

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

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

@Unsafe
注释的方法的列表。

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

3
投票

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

一个复杂之处是标准注释处理器不会深入到方法体中(它只检查方法声明)。 您需要一个可以检查每一行代码的注释处理器。

Checker Framework 旨在构建此类注释处理器。 您只需要定义一个回调,在给定方法调用的情况下,如果调用不可接受,则发出 javac 警告。 (在您的情况下,这只是方法的声明是否具有

@Unsafe
注释。)Checker 框架在程序中的每个方法调用上运行该回调。


1
投票

下面的 AbstractProcessor 处理 greghmerrill 的 @Unsafe 注释,并在调用 @Unsafe 注释方法时发出警告。

这是对 greghmerrills 自己的答案的轻微修改,这很棒,但是我在让 IDE 增量编译器(我使用 Netbeans)检测插件发出的警告/错误等时遇到了一些问题 - 仅那些我从处理器打印的内容已显示,但当我运行“mvn cleancompile”(我使用的是 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<? 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) { } }

当使用注释并调用带注释的方法时,它将如下所示: enter image description here

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

在 com.sun** 导入方面遇到问题的 Maven 用户可能会发现来自 AnimeshSharma 的 这个答案 有帮助。

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

<build>
    <pluginManagement>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <compilerArgument>-proc:none</compilerArgument>
                </configuration>
            </plugin>
        </plugins>
    </pluginManagement>
</build>

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

最后应该提到的是,我是 AbstractProcessors 和 TaskListeners 的新手。我确实没有对代码的性能或鲁棒性有一个概述。目标只是“让它发挥作用”并为类似项目提供存根。

© www.soinside.com 2019 - 2024. All rights reserved.