代码之家  ›  专栏  ›  技术社区  ›  pimlottc

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

  •  71
  • pimlottc  · 技术社区  · 15 年前

    我将进行一次丑陋的临时黑客攻击,以便在等待外部资源修复时解决阻塞问题。除了用一个可怕的大注释和一堆修正码标记它之外,我还希望让编译器抛出一条明显的警告消息作为提醒,这样我们就不会忘记删除它。例如,类似于:

    [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 Javac1.6(从Ant脚本运行)中没有编译器或运行时错误,它肯定在执行函数。

    11 回复  |  直到 6 年前
        1
  •  32
  •   Kevin Day    15 年前

    我见过的一种技术是将它与单元测试联系起来(你 单元测试,对吗?).基本上,创建一个单元测试, 失败 一旦外部资源修复完成。然后,您评论单元测试,告诉其他人如何在问题解决后撤销您的粗糙的黑客攻击。

    这种方法真正巧妙的地方在于,撤销黑客攻击的触发因素是修复核心问题本身。

        2
  •  78
  •   barjak    12 年前

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

    • 编写自定义批注类型。 This page 解释如何编写注释。
    • 编写注释处理器,处理自定义注释以发出警告。运行此类注释处理器的工具称为apt。您可以在 this page . 我认为APTAPI中需要的是AnnotationProcessorEnvironment,它允许您发出警告。
    • 从Java 6中,APT被集成到Javac中。也就是说,可以在javac命令行中添加注释处理器。 This section javac手册将告诉您如何调用自定义注释处理器。

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

    编辑

    我成功地实现了我的解决方案。作为奖励,我使用Java的服务提供商设施来简化它的使用。实际上,我的解决方案是一个包含两个类的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作为服务提供者,请添加文件 META-INF/services/javax.annotation.processing.Processor 在罐子里。此文件是ACSII文件,必须包含以下文本:

    fr.barjak.hack_processor.Processor
    
        3
  •  12
  •   WReach    13 年前

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

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

    此未使用的变量将生成一个警告(取决于编译器),该警告看起来如下:

    WARNING: The local variable FIXMEtemporaryHackToWorkAroundLibraryBugRemoveMeWhenLibraryIsFixed is never read.

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

        4
  •  12
  •   Luchostein    10 年前

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

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

    这将生成一个警告,因为编译器无法将其识别为要禁止显示的特定警告:

    不支持@suppresswarnings(“fixme:这是一个黑客,应该是 固定的。”

        5
  •  8
  •   Ben Leggiero    8 年前

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

    使用非常简单:

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

    编译器将在您的文本中抛出警告消息

        6
  •  5
  •   Peter Recore    15 年前

    将方法或类标记为@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");
        }
    }
    

    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的Javac1.6。如果您想要一个诚实的善意警告而不仅仅是一个注释,请使用-xlint选项。也许这会正确地渗透到蚂蚁身上。

        7
  •  3
  •   Radiodef    6 年前

    我们可以通过注释来实现这一点!

    要引发错误,请使用 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
      interface Example {}
      
    • @Marker
      interface Example extends Serializable {}
      

    但这些用途 @标记 将导致编译器错误:

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

      marker error

    这是一篇博客文章,我发现在开始这个主题时非常有用:


    小提示:下面的评论指出的是因为 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 无法引用的类型 @安 直接,因为它将创建一个循环JAR依赖项。它只能参考 @安 通过 String 姓名或 TypeElement / TypeMirror .

        8
  •  2
  •   Lukas Eder    12 年前

    Here 显示了有关注释的教程,在底部给出了定义自己注释的示例。不幸的是,一个快速浏览教程说,这些只有在javadoc中才可用…

    编译器使用的注释有三种注释类型 由语言规范本身预定义的:@已弃用, @override和@suppresswarnings。

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

        9
  •  0
  •   Lioda    15 年前

    您应该使用一个工具来编译,比如Ant-Ou-Maven。使用它,您应该在编译时定义一些任务,例如,这些任务可以生成关于fixme标记的一些日志(如消息或警告)。

    如果你想要一些错误,这也是可能的。比如在代码中留下一些待办事项时停止编译(为什么不这样做?)

        10
  •  0
  •   Andy Balaam    8 年前

    为了让任何警告出现,我发现未使用的变量和custom@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
    
        11
  •  0
  •   BARJ    8 年前

    如果你用的是Intellij。您可以转到:preferences>editor>todo并添加“\bhack.b*”或任何其他模式。

    如果你这样评论 // HACK: temporary fix to work around server issues

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