如何故意导致自定义Java编译器警告消息?


83

我将提交一个丑陋的临时hack,以便在我们等待外部资源修复时解决阻塞问题。除了用一个可怕的注释和大量的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脚本运行)中,没有编译器或运行时错误,并且肯定是在执行该功能。


1
仅供参考:@Deprecated仅给出编译器警告,而不给出编译器或运行时错误。该代码肯定应该运行
BalusC

尝试直接使用javac运行。我怀疑蚂蚁隐藏了一些输出。或查看下面更新的答案以了解更多详细信息。
Peter Recore 09年

Answers:


42

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

这种方法真正令人眼前一亮的是,撤消黑客的诱因是对核心问题本身的修复。


2
我在No Fluff Just Stuff会议之一中听说过此事(不记得演示者是谁)。我以为它很光滑。我绝对推荐那些会议。
凯文·戴

3
我想看看这种方法的一个例子
birgersp

答案是11岁,但我什至更进一步:注释单元测试很危险。我将创建一个单元测试,将不希望的行为封装起来,这样一来,当最终修复该问题时,便会中断。
avgvstvs

86

我认为将由编译器处理的自定义批注是解决方案。我经常编写自定义批注以在运行时执行操作,但是我从未尝试在编译时使用它们。因此,我只能为您提供可能需要的工具的指针:

  • 编写自定义注释类型。本页说明如何编写注释。
  • 编写注释处理器,处理您的自定义注释以发出警告。运行此类注释处理器的工具称为APT。您可以在此页面上找到介绍。我认为您需要在APT API中使用AnnotationProcessorEnvironment,它可以让您发出警告。
  • 从Java 6开始,APT已集成到javac中。也就是说,您可以在javac命令行中添加注释处理器。javac手册的这一部分将告诉您如何调用自定义注释处理器。

我不知道这种解决方案是否切实可行。我会在有空的时候尝试自己实现它。

编辑

我成功实现了我的解决方案。另外,我使用了Java的服务提供商功能来简化其使用。实际上,我的解决方案是一个jar,其中包含2个类:自定义注释和注释处理器。要使用它,只需将此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

3
+1,好研究!绝对是这样做的“正确方法”(如果单元测试不可行),并且它具有超越常规警告的优势。
Yishai

1
javac发出警告,但是eclipse(?)中什么都没有发生
2012年

8
小提示:无需覆盖init和设置env字段-因为它是ProcessingEnvironmentthis.processingEnv所以您可以从中获取protected
Paul Bellora 2012年

该警告消息在IDE警告中是否可见?
uylmz 2014年

4
在Eclipse中,注释处理默认是关闭的。要打开它,请转到项目属性-> Java编译器->注释处理->启用注释处理。然后,在该页面下面是一个名为“工厂路径”的页面,您需要在其中配置具有要使用的处理器的jar。
Konstantin Komissarchik 2014年

14

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

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

这将生成警告,因为编译器未将其识别为特定的警告,以禁止执行以下操作:

不支持的@SuppressWarnings(“ FIXME:这是一个hack,应该修复。”)


4
它不能抑制字段可见性警告或棉绒错误。
IgorGanapolsky '16

2
具有讽刺意味的是分心。
好客的

12

一个好的技巧应该得到另一个...我通常会通过在hacky方法中引入未使用的变量来生成用于上述目的的编译器警告,因此:

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

这个未使用的变量将生成一个警告,具体取决于您的编译器:

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

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


您知道如何启用未使用的变量警告吗?我从命令行使用Gradle构建Android系统,未收到未使用变量的任何警告。您知道如何在中启用它build.gradle吗?
安德里亚斯(Andreas)

@Andreas对不起,我对该环境/工具链一无所知。如果还没有关于此主题的SO问题,则可以考虑问一个问题。
WReach

10

我编写了一个带有注释的库:Lightweight Javac @Warning Annotation

用法很简单:

// some code...

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

编译器将在您的文本中引发警告消息


请添加免责声明您是推荐库的作者。
Paul Bellora 2015年

@PaulBellora不知道它将如何为您提供帮助,但是可以
Artem Zinnatullin 2015年


5

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

UPDATE是我刚刚测试过的代码: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。
pimlottc

1
嗯。您的示例仅显示不推荐使用的方法。您在哪里使用该方法?这就是警告出现的地方。
Peter Recore 09年

3
为了记录起见,@Deprecated仅可跨类使用(因此对于私有方法没有用)。
npostavs 2012年

4

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

要引发错误,请使用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在编译应用程序JAR时使用:

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

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


这并不是编写注释处理器的最佳方法。通常,您可以从Set<? extends TypeElement>参数获取注释类型,然后使用来获取给定回合的带注释元素getElementsAnnotatedWith(TypeElement annotation)。我也不明白你为什么要包装这种printMessage方法。
ThePyroEagle '16

@ThePyroEagle两个重载之间的选择肯定是编码样式上的很小差异。
Radiodef

但这是理想情况,您是否只想在处理器JAR中安装注释处理器?使用前面提到的方法可以实现这种隔离级别,因为您无需在类路径中具有已处理的注释。
ThePyroEagle,2013年

2

这里显示了有关注释的教程,在底部给出了定义自己的注释的示例。不幸的是,快速浏览本教程后发现,这些仅在javadoc中可用。

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

因此,您似乎真正能做的只是抛出一个@Deprecated标记,编译器将打印出该标记或将一个自定义标记放入告诉黑客的javadocs中。


编译器也会发出警告,指出您用@Deprecated标记的方法是如此...它将告诉用户它是哪个冒犯对象。
马特·菲利普斯

1

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

如果您再发表这样的评论 // HACK: temporary fix to work around server issues

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


0

您应该使用像ant ou maven这样的工具进行编译。使用它,您应该在编译时定义一些任务,例如,这些任务可能会生成一些有关FIXME标签的日志(例如消息或警告)。

而且,如果您想要一些错误,也有可能。当您在代码中留下一些TODO时,就像停止编译一样(为什么不呢?)


hack是让它尽快运行,我现在没有时间更改构建系统:)但为将来考虑很好...
pimlottc

0

为了完全显示任何警告,我发现未使用的变量和自定义@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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.