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

当使用带注释的方法(如@deprecated)时,使java编译器发出警告,java,compiler-construction,annotations,deprecated,Java,Compiler Construction,Annotations,Deprecated,假设我定义了一个名为@safe的自定义注释 我想提供一个注释处理器,它将检测对使用@Unsafe注释的方法的引用,并打印一条警告 例如,给定此代码 public class Foo { @Unsafe public void doSomething() { ... } } public class Bar { public static void main(String[] args) { new Foo().doSomething(); } } 。。。我希望编译器打

假设我定义了一个名为
@safe
的自定义注释

我想提供一个注释处理器,它将检测对使用
@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和注释处理以一种更自然的方式来完成它?

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

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


设计用于构建此类注释处理器。您只需要定义一个回调函数,该函数给定一个方法调用,并在调用不可接受时发出javac警告。(在你的例子中,问题很简单,就是方法的声明是否有
@Unsafe
注释。)Checker框架在程序中的每个方法调用上都运行该回调。

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

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

然后,我开发了一个javac插件。插件API使得在AST中查找任何方法的每次调用都很容易。通过使用中的一些提示,我能够从MethodInvocationTree AST节点确定类和方法名。然后,我将这些方法调用与我创建的早期“列表”进行比较,其中包含用
@safe
注释的方法,并在需要时发出警告

这里是我的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) {
  }

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

com.unsafetest.javac.UnsafePlugin

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

这是对greghmerrills自己的答案的一个轻微修改,这很好,但我在让我的IDEs增量编译器(我使用Netbeans)检测插件发出的警告/错误等时遇到了一些问题-只显示了我从处理器打印的警告/错误,尽管运行“mvn clean compile”时的行为与预期一样(我正在使用Maven)。这是由于我手上的一些问题,还是由于插件和抽象处理器之间的差异/编译过程的各个阶段,我不知道

无论如何:

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) { } }
package com.hervian.annotationutils.target;
导入com.sun.source.tree.MethodInvocationTree;
导入com.sun.source.util.*;
导入com.sun.tools.javac.tree.JCTree;
导入com.sun.tools.javac.tree.TreeInfo;
导入java.util.Set;
导入javax.annotation.processing.*;
导入javax.lang.model.SourceVersion;
导入javax.lang.model.element.*;
导入javax.tools.Diagnostic;
@SupportedAnnotationTypes({“com.hervian.annotationutils.target.Unsafe”})
@SupportedSourceVersion(SourceVersion.RELEASE_8)
公共类UnsafeAnnotationProcessor扩展AbstractProcessor实现TaskListener{
树木;
@凌驾
公共同步void init(ProcessingEnvironment ProcessingEnvironment){
super.init(processingEnv);
trees=trees.instance(processingEnv);
instance(processingEnv).setTaskListener(this);
}
@凌驾

公共布尔过程(SET)您确定在编译时完全可以这样做吗?不可能总是知道引用了什么方法。假设您的
Foo
正在实现一个接口,而
@safe
注释仅在
Foo
实现上。那么使用该接口的客户端将不会出现。您想打印警告吗关于调用该方法的代码行,因此您肯定无法避免字节码分析。@sisyphus,如果对接口方法的调用可能不安全,那么接口方法应该被注释为
@safe
。因此,编译时处理是完整的。@user3707125,不需要字节码分析,因为注释处理器甚至可以在方法体中进行源代码分析。对于这个问题,我们可以假设
@safe
的行为与
@Deprecated
的行为完全相同。我明白你的意思了@sisyphus,你是说
@safe
的行为应该与“synchronized”类似修饰符,也就是说,您确实必须手头有实现才能知道它是否同步。但是为了简化,让我们假设它的工作原理与
@Deprecated
完全相同,也就是说,我可以只查看编译的类型,而不必担心运行时替换。感谢您提供了指向检查器框架的指针。如果我理解这一点的话正确地说,我不可能用标准的javax.annotation.processing.Processor和JDK javac来实现这一点,因为我看到Checker通过为javac提供自己的替代物来实现其处理(我假设它在内部委托给“真正的”javac)?Checker框架可以使用标准的javac。它的javac有两个效果:(1)添加Checker框架
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) { } }
<build>
    <pluginManagement>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <compilerArgument>-proc:none</compilerArgument>
                </configuration>
            </plugin>
        </plugins>
    </pluginManagement>
</build>