如何在 Java 中实现构建特定注释保留

How to implement build specific annotation retention in Java

我有一个注释,目前仅用于内部构建和文档目的。它在 运行 时不提供任何价值,这就是我选择 @Retention(SOURCE):

的原因
@Retention(SOURCE)
public @interface X

但是,为了验证它的正确用法,我想实现一个单元测试来导航整个 API 以检查注释是否应用到它应该应用的所有地方。通过使用普通的 Java 反射 APIs 可以很容易地实现该单元测试,但我不能这样做,因为测试无法反映注释,因为它是 @Retention(SOURCE).

为了在测试中使用反射,我必须将其更改为 @Retention(RUNTIME),由于 运行 时字节代码的开销,我想避免这种情况。

我知道的解决方法:

一如既往的解决方法。我知道这些:

问题:

是否有更方便的方法来使用 Maven 在 运行 时间仅用于测试,而不是在实际构建的 jar 文件中保留注释?

我认为您很好地涵盖了解决方案 space。

还有两个你没有讲到:

  • 稍后在 post 处理步骤中使用 proguard 等工具删除注释。

  • 破解你的编译器以根据标志切换注释保留。很确定您可以在内部元数据中切换一些标志。可能由注解 @DynamicRetention("flag")?

  • 触发的另一个注解处理器注入

其他解决方法之一可能包括:

  1. 保留默认值 retention = CLASS
  2. 使用将读取 字节码 直接.
  3. 的库
@interface X {
}

@X
public class Main {
  public static void main(String[] args) throws IOException {
    ClassPathResource classResource = new ClassPathResource("com/caco3/annotations/Main.class");
    try (InputStream is = classResource.getInputStream()) {
      ClassReader classReader = new ClassReader(is);
      AnnotationMetadataReadingVisitor visitor = new AnnotationMetadataReadingVisitor(Main.class.getClassLoader());
      classReader.accept(visitor, 0);
      System.out.println(visitor.getAnnotationTypes());
    }
  }
}

产量:

[com.caco3.annotations.X]

使用的库是ASM:

ASM is an all purpose Java bytecode manipulation and analysis framework

此代码使用 Spring 框架中的一些 类:

但是这种方法存在与您描述的相同的缺点:

overhead in byte code at run time

因为(来自javadoc):

Annotations are to be recorded in the class file by the compiler but need not be retained by the VM at run time.

public static void main(String[] args) throws IOException {
    X x = AnnotationUtils.findAnnotation(Main.class, X.class);
    System.out.println(x);
}

输出:null

这是一种可能有效的混合方法。

编写一个注解处理器,它不执行您想要执行的完整测试,而是仅在出现注解的边车文件中进行记录。如果您要注释 classes、方法和字段,可以使用 package-qualified class 名称加上方法或字段描述符相当直接地记录位置。 (不过,如果您的注释可以出现在更隐蔽的地方,例如方法参数或类型使用站点,这可能会更困难。)然后,您可以将保留策略保持为 SOURCE.

接下来,编写你的 junit 测试来做你打算做的任何反射分析。与其尝试反射性地查找注释,不如(因为它们不会在那里)读入 sidecar 文件并查看那里。

如果@Retention(CLASS)可以接受,那么我会推荐使用ArchUnit。您描述的任务听起来很合适。 ArchUnit 可用于为您的架构定义和验证规则。例如,它可用于限制某些 classes/packages 之间的访问,或者例如验证 class 层次结构、类型名称 - 或注释。

它通常作为单元测试由 JUnit 或任何其他测试框架执行。它通过分析字节码来工作,因此无需切换到运行时保留。

流利的 API 很好,在我看来,比使用反射或注释处理这个用例更具可读性。例如,要确保某些 classes 应该始终具有特定的注释,您可以在单元测试中编写此规则:

classes().that().areAssignableTo(MyService.class).should().beAnnotatedWith(MyAnnotation.class)

也可以创建自定义规则来声明更复杂的约束。