【问题标题】:How to intentionally cause a custom java compiler warning message?如何故意导致自定义 java 编译器警告消息?
【发布时间】:2010-12-17 16:18:24
【问题描述】:

我将提交一个丑陋的临时黑客,以便在我们等待外部资源修复时解决阻塞问题。除了用一个可怕的评论和一堆 FIXME 来标记它之外,我希望编译器抛出一个明显的警告消息作为提醒,这样我们就不会忘记把它拿出来。例如,类似:

[javac] com.foo.Hacky.java:192: warning: FIXME temporary hack to work around library bug, remove me when library is fixed!

有没有办法可以通过我选择的消息引起故意的编译器警告?如果做不到这一点,添加到代码中以引发现有警告的最简单的方法是什么,可能在违规行的字符串中包含一条消息,以便将其打印在警告消息中?

编辑: 弃用的标签似乎对我没有任何帮助:

/**
 * @deprecated "Temporary hack to work around remote server quirks"
 */
@Deprecated
private void doSomeHackyStuff() { ... }

在 eclipse 或 sun javac 1.6(从 ant 脚本运行)中没有编译器或运行时错误,它肯定正在执行该函数。

【问题讨论】:

  • 仅供参考:@Deprecated 仅给出编译器警告,而不是编译器或运行时错误。代码绝对应该运行
  • 尝试直接用javac运行。我怀疑 Ant 隐藏了一些输出。或查看下面我更新的答案以获取更多详细信息。

标签: java compiler-warnings javac


【解决方案1】:

我们可以通过注释来做到这一点!

要引发错误,请使用Messager 发送带有Diagnostic.Kind.ERROR 的消息。简短的例子:

processingEnv.getMessager().printMessage(
    Diagnostic.Kind.ERROR, "Something happened!", element);

这是我写的一个相当简单的注释,只是为了测试一下。

这个@Marker注解表明目标是一个标记接口:

package marker;

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Marker {
}

如果不是,注释处理器会导致错误:

package marker;

import javax.annotation.processing.*;
import javax.lang.model.*;
import javax.lang.model.element.*;
import javax.lang.model.type.*;
import javax.lang.model.util.*;
import javax.tools.Diagnostic;
import java.util.Set;

@SupportedAnnotationTypes("marker.Marker")
@SupportedSourceVersion(SourceVersion.RELEASE_6)
public final class MarkerProcessor extends AbstractProcessor {

    private void causeError(String message, Element e) {
        processingEnv.getMessager()
            .printMessage(Diagnostic.Kind.ERROR, message, e);
    }

    private void causeError(
            Element subtype, Element supertype, Element method) {
        String message;
        if (subtype == supertype) {
            message = String.format(
                "@Marker target %s declares a method %s",
                subtype, method);
        } else {
            message = String.format(
                "@Marker target %s has a superinterface " +
                "%s which declares a method %s",
                subtype, supertype, method);
        }

        causeError(message, subtype);
    }

    @Override
    public boolean process(
            Set<? extends TypeElement> annotations,
            RoundEnvironment roundEnv) {

        Elements elementUtils = processingEnv.getElementUtils();
        boolean processMarker = annotations.contains(
            elementUtils.getTypeElement(Marker.class.getName()));
        if (!processMarker)
            return false;

        for (Element e : roundEnv.getElementsAnnotatedWith(Marker.class)) {
            ElementKind kind = e.getKind();

            if (kind != ElementKind.INTERFACE) {
                causeError(String.format(
                    "target of @Marker %s is not an interface", e), e);
                continue;
            }

            if (kind == ElementKind.ANNOTATION_TYPE) {
                causeError(String.format(
                    "target of @Marker %s is an annotation", e), e);
                continue;
            }

            ensureNoMethodsDeclared(e, e);
        }

        return true;
    }

    private void ensureNoMethodsDeclared(
            Element subtype, Element supertype) {
        TypeElement type = (TypeElement) supertype;

        for (Element member : type.getEnclosedElements()) {
            if (member.getKind() != ElementKind.METHOD)
                continue;
            if (member.getModifiers().contains(Modifier.STATIC))
                continue;
            causeError(subtype, supertype, member);
        }

        Types typeUtils = processingEnv.getTypeUtils();
        for (TypeMirror face : type.getInterfaces()) {
            ensureNoMethodsDeclared(subtype, typeUtils.asElement(face));
        }
    }
}

例如,这些是@Marker的正确用法:

  • @Marker
    interface Example {}
    
  • @Marker
    interface Example extends Serializable {}
    

但是@Marker 的这些用法会导致编译器错误:

  • @Marker
    class Example {}
    
  • @Marker
    interface Example {
        void method();
    }
    

这是我发现对开始学习该主题很有帮助的一篇博文:


小记:下面的评论员指出的是因为MarkerProcessor引用Marker.class,所以它对它有编译时依赖。我写了上面的例子,假设两个类都放在同一个 JAR 文件中(比如,marker.jar),但这并不总是可能的。

例如,假设有一个包含以下类的应用程序 JAR:

com.acme.app.Main
com.acme.app.@Ann
com.acme.app.AnnotatedTypeA (uses @Ann)
com.acme.app.AnnotatedTypeB (uses @Ann)

那么@Ann 的处理器存在于单独的JAR 中,在编译应用程序JAR 时使用:

com.acme.proc.AnnProcessor (processes @Ann)

在这种情况下,AnnProcessor 将无法直接引用 @Ann 的类型,因为它会创建循环 JAR 依赖项。它只能通过String 名称或TypeElement/TypeMirror 引用@Ann

【讨论】:

  • 这并不是编写注释处理器的最佳方式。您通常从Set&lt;? extends TypeElement&gt; 参数获取注释类型,然后使用getElementsAnnotatedWith(TypeElement annotation) 获取给定回合的注释元素。我也不明白你为什么要包装printMessage 方法。
  • @ThePyroEagle 两个重载之间的选择肯定是编码风格上的一个非常小的差异。
  • 但理想情况下,您不想在处理器 JAR 中只使用注释处理器吗?使用前面提到的方法可以实现该级别的隔离,因为您不需要在类路径中包含已处理的注释。
【解决方案2】:

如果您使用的是 IntelliJ。您可以转到:Preferences>Editor>TODO 并添加“\bhack.b*”或任何其他模式。

如果您随后发表评论,例如// HACK: temporary fix to work around server issues

然后在 TODO 工具窗口中,它将与您定义的所有其他模式一起很好地显示,同时进行编辑。

【讨论】:

    【解决方案3】:

    为了让任何警告出现,我发现未使用的变量和自定义 @SuppressWarnings 对我不起作用,但不必要的演员表却可以:

    public class Example {
        public void warn() {
            String fixmePlease = (String)"Hello";
        }
    }
    

    现在当我编译时:

    $ javac -Xlint:all Example.java
    ExampleTest.java:12: warning: [cast] redundant cast to String
            String s = (String) "Hello!";
                       ^
    1 warning
    

    【讨论】:

      【解决方案4】:

      我写了一个库,用注释来做到这一点:Lightweight Javac @Warning Annotation

      用法很简单:

      // some code...
      
      @Warning("This method should be refactored")
      public void someCodeWhichYouNeedAtTheMomentButYouWantToRefactorItLater() {
          // bad stuff going on here...
      }
      

      编译器会在你的文本中抛出警告信息

      【讨论】:

      【解决方案5】:

      一些快速且不那么肮脏的方法,可能是使用带有故意错误的String 参数的@SuppressWarnings 注释:

      @SuppressWarnings("FIXME: this is a hack and should be fixed.")
      

      这将生成一个警告,因为编译器不会将其识别为要抑制的特定警告:

      不支持 @SuppressWarnings("FIXME:这是一个 hack,应该是 固定。”)

      【讨论】:

      • 它在抑制字段可见性警告或 lint 错误方面不起作用。
      • 讽刺是分散注意力。
      【解决方案6】:

      Here 显示了一个关于注解的教程,在底部它给出了一个定义你自己的注解的例子。不幸的是,快速浏览本教程说这些仅在 javadoc 中可用......

      编译器使用的注解共有三种注解类型 由语言规范本身预定义的:@Deprecated, @Override 和 @SuppressWarnings。

      所以看来,你真正能做的就是添加一个@Deprecated 标记,编译器将打印出该标记,或者在javadocs 中放置一个自定义标记来讲述黑客攻击。

      【讨论】:

      • 编译器也会发出警告,说你用@Deprecated 标记的方法是如此...它会告诉用户它是哪个违规的。
      【解决方案7】:

      我认为由编译器处理的自定义注释是解决方案。我经常编写自定义注释来在运行时做事,但我从未尝试在编译时使用它们。所以,我只能给你一些你可能需要的工具的指导:

      • 编写自定义注释类型。 This page 解释了如何编写注释。
      • 编写注释处理器,处理您的自定义注释以发出警告。运行此类注释处理器的工具称为 APT。您可以在this page 上找到介绍。我认为您在 APT API 中需要的是 AnnotationProcessorEnvironment,它可以让您发出警告。
      • 从 Java 6 开始,APT 被集成到 javac 中。也就是说,您可以在 javac 命令行中添加注释处理器。 javac 手册的This section 会告诉你如何调用你的自定义注解处理器。

      我不知道这个解决方案是否真的可行。有时间我会尝试自己实现。

      编辑

      我成功实施了我的解决方案。作为奖励,我使用了 java 的服务提供者工具来简化它的使用。实际上,我的解决方案是一个包含 2 个类的 jar:自定义注释和注释处理器。要使用它,只需将此 jar 添加到项目的类路径中,并注释您想要的任何内容!这在我的 IDE (NetBeans) 中运行良好。

      注解代码:

      package fr.barjak.hack;
      
      import java.lang.annotation.ElementType;
      import java.lang.annotation.Retention;
      import java.lang.annotation.RetentionPolicy;
      import java.lang.annotation.Target;
      
      @Retention(RetentionPolicy.SOURCE)
      @Target({ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.LOCAL_VARIABLE, ElementType.METHOD, ElementType.PACKAGE, ElementType.PARAMETER, ElementType.TYPE})
      public @interface Hack {
      
      }
      

      处理器代码:

      package fr.barjak.hack_processor;
      
      import java.util.Set;
      import javax.annotation.processing.AbstractProcessor;
      import javax.annotation.processing.ProcessingEnvironment;
      import javax.annotation.processing.RoundEnvironment;
      import javax.annotation.processing.SupportedAnnotationTypes;
      import javax.lang.model.element.Element;
      import javax.lang.model.element.TypeElement;
      import javax.tools.Diagnostic.Kind;
      
      @SupportedAnnotationTypes("fr.barjak.hack.Hack")
      public class Processor extends AbstractProcessor {
      
          private ProcessingEnvironment env;
      
          @Override
          public synchronized void init(ProcessingEnvironment pe) {
              this.env = pe;
          }
      
          @Override
          public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
              if (!roundEnv.processingOver()) {
                  for (TypeElement te : annotations) {
                      final Set< ? extends Element> elts = roundEnv.getElementsAnnotatedWith(te);
                      for (Element elt : elts) {
                          env.getMessager().printMessage(Kind.WARNING,
                                  String.format("%s : thou shalt not hack %s", roundEnv.getRootElements(), elt),
                                  elt);
                      }
                  }
              }
              return true;
          }
      
      }
      

      要将生成的 jar 启用为服务提供者,请在 jar 中添加文件 META-INF/services/javax.annotation.processing.Processor。此文件是 acsii 文件,必须包含以下文本:

      fr.barjak.hack_processor.Processor
      

      【讨论】:

      • +1,伟大的研究!这绝对是做到这一点的“正确方法”(如果单元测试不实用),它的优势在于超越常规警告。
      • javac 发出警告,但 eclipse(?) 中没有任何反应
      • 小提示:无需覆盖init 并设置env 字段-您可以从this.processingEnv 获取ProcessingEnvironment,因为它是protected
      • IDE 警告中会显示此警告消息吗?
      • 注释处理在 Eclipse 中默认是关闭的。要打开它,请转到项目属性 -> Java 编译器 -> 注释处理 -> 启用注释处理。然后在该页面下方是一个名为“Factory Path”的页面,您需要在其中配置具有您要使用的处理器的 jar。
      【解决方案8】:

      一个好的 hack 值得另一个......我通常通过在 hacky 方法中引入一个未使用的变量来为所描述的目的生成编译器警告,因此:

      /**
       * @deprecated "Temporary hack to work around remote server quirks"
       */
      @Deprecated
      private void doSomeHackyStuff() {
          int FIXMEtemporaryHackToWorkAroundLibraryBugRemoveMeWhenLibraryIsFixed;
          ...
      }
      

      这个未使用的变量将生成一个警告,它(取决于你的编译器)看起来像这样:

      警告:永远不会读取局部变量 FIXMEtemporaryHackToWorkAroundLibraryBugRemoveMeWhenLibraryIsFixed。

      此解决方案不如自定义注释好,但它的优点是不需要提前准备(假设编译器已配置为对未使用的变量发出警告)。我建议这种方法只适用于短暂的黑客攻击。对于长期存在的 hack,我认为创建自定义注释的努力是合理的。

      【讨论】:

      • 你知道如何启用未使用的变量警告吗?我正在从命令行使用 Gradle 为 Android 构建,我没有收到任何未使用变量的警告。你知道如何在build.gradle 中启用它吗?
      • @Andreas 抱歉,我对那个环境/工具链一无所知。如果还没有关于此主题的 SO 问题,您可以考虑问一个。
      【解决方案9】:

      如何将方法或类标记为@Deprecated? docs here。请注意,同时存在@Deprecated 和@deprecated - 大写的 D 版本是注释,小写的 d 是 javadoc 版本。 javadoc 版本允许您指定一个任意字符串来解释正在发生的事情。但是编译器在看到它时不需要发出警告(尽管很多人这样做)。注释应始终引起警告,但我认为您无法为其添加解释。

      这里更新的是我刚刚测试过的代码: Sample.java 包含:

      public class Sample {
          @Deprecated
          public static void foo() {
               System.out.println("I am a hack");
          }
      }
      

      SampleCaller.java 包含:

      public class SampleCaller{
           public static void main(String [] args) {
               Sample.foo();
           }
      }
      

      当我运行“javac Sample.java SampleCaller.java”时,我得到以下输出:

      Note: SampleCaller.java uses or overrides a deprecated API.
      Note: Recompile with -Xlint:deprecation for details.
      

      我正在使用 sun 的 javac 1.6。如果您想要一个诚实至善的警告而不仅仅是一个注释,请使用 -Xlint 选项。也许这会正确地渗透到 Ant 中。

      【讨论】:

      • 我似乎没有从使用 @Deprecate 的编译器中得到错误;使用示例代码编辑我的 q。
      • 嗯。您的示例仅显示不推荐使用的方法。你在哪里使用该方法?这就是显示警告的地方。
      • 郑重声明,@Deprecated 仅适用于跨类(因此对私有方法无用)。
      【解决方案10】:

      我见过使用的一种技术是将其与单元测试联系起来(您进行单元测试,对吗?)。基本上,一旦实现外部资源修复,您就会创建一个失败的单元测试。然后,您对该单元测试进行评论,以告诉其他人在问题解决后如何撤消您的粗糙破解。

      这种方法的真正巧妙之处在于,撤消黑客攻击的触发器是对核心问题本身的修复。

      【讨论】:

      • 我在一次 No Fluff Just Stuff 会议上听说了这件事(不记得演示者是谁)。我认为它很光滑。不过,我绝对推荐这些会议。
      • 我想看看这种方法的一个例子
      • 答案已经 11 岁了,但我什至会更进一步:评论单元测试是危险的。我会创建一个单元测试来封装不良行为,这样当它最终得到修复时,编译就会中断。
      【解决方案11】:

      你应该使用一个工具来编译,比如ant ou maven。有了它,您应该在编译时定义一些任务,这些任务可能会产生一些关于您的 FIXME 标签的日志(如消息或警告)。

      如果你想要一些错误,它也是可能的。就像在代码中留下一些 TODO 时停止编译(为什么不呢?)

      【讨论】:

      • 诀窍是让它尽快工作,我现在没有时间更改构建系统 :) 但是很好地考虑未来...
      猜你喜欢
      • 2014-04-12
      • 1970-01-01
      • 2014-08-07
      • 1970-01-01
      • 2017-06-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多