NonNull Lombok构建器属性的FindBugs检测器

时间:2018-07-13 12:16:54

标签: java builder findbugs lombok null-check

我有很多使用Lombok构建器的@NonNull字段类。

@Builder
class SomeObject {
    @NonNull String mandatoryField1;
    @NonNull String mandatoryField2;
    Integer optionalField;
    ...
}

但是,这为调用者提供了创建对象的选项,而无需设置mandatoryField,这在使用时会导致运行时失败。

SomeObject.builder()
          .mandatoryField1("...")
          // Not setting mandatoryField2
          .build();

我正在寻找在构建时捕获这些错误的方法。

有非Lombok方法,例如StepBuilders或构造函数,可确保始终设置必填字段,但我对使用Lombok构建器实现此目的的方法感兴趣。

此外,我了解设计类(例如逐步构建器或@AllArgsConstructor)以进行编译时检查会创建很多笨拙的代码-这就是为什么我有动力去构建编译后的FindBugs步骤可以检测到这些。

现在,当我将@NonNull字段显式设置为null时,FindBugs确实失败了:

FindBugs检测到此失败,

new SomeObject().setMandatoryField1(null);

但是它没有检测到这个:

SomeObject.builder()
          .mandatoryField1(null)
          .build();

它也不会检测到此情况:

SomeObject.builder()
          .mandatoryField1("...")
          //.mandatoryField2("...") Not setting it at all.
          .build();

这似乎是由于Delomboked构建器看起来像

public static class SomeObjectBuilder {
    private String mandatoryField1;
    private String mandatoryField2;
    private Integer optionalField;

    SomeObjectBuilder() {}

    public SomeObjectBuilder mandatoryField1(final String mandatoryField1) {
        this.mandatoryField1 = mandatoryField1;
        return this;
    }

    // ... other chained setters.

    public SomeObject build() {
        return new SomeObject(mandatoryField1, mandatoryField2, optionalField);
    }
}

我注意到:

  • Lombok不会在其内部字段中添加任何@NonNull,也不会在非null字段中添加任何null检查。
  • 对于FindBugs捕获这些故障,它不会调用任何SomeObject.set*方法。

我有以下问题:

  • 如果设置了@NonNull属性,是否有办法以导致构建时失败的方式使用Lombok构建器(在运行FindBugs时,否则)?
  • 是否有任何自定义的FindBugs检测器可以检测到这些故障?

2 个答案:

答案 0 :(得分:6)

Lombok在生成@NonNull时会考虑这些@AllArgsConstructor注释。这也适用于@Builder生成的构造函数。这是示例中构造函数的代码:

SomeObject(@NonNull final String mandatoryField1, @NonNull final String mandatoryField2, final Integer optionalField) {
    if (mandatoryField1 == null) {
        throw new java.lang.NullPointerException("mandatoryField1 is marked @NonNull but is null");
    }
    if (mandatoryField2 == null) {
        throw new java.lang.NullPointerException("mandatoryField2 is marked @NonNull but is null");
    }
    this.mandatoryField1 = mandatoryField1;
    this.mandatoryField2 = mandatoryField2;
    this.optionalField = optionalField;
}

因此,FindBugs在理论上可以找到问题,因为在构造函数中存在空检查,此后在示例中使用null值对其进行调用。但是,FindBugs可能还不够强大(还可以吗?),我还不知道有任何定制的检测器可以做到这一点。

问题仍然存在,为什么lombok不会将那些检查添加到构建器的setter方法中(这会使FindBugs更容易发现问题)。这是因为与仍将@NonNull字段设置为null的构建器实例一起使用是完全合法的。考虑以下用例:

例如,您可以使用toBuilder()方法从实例创建新的生成器,然后通过调用mandatoryField1(null)删除其强制字段之一(也许是因为您要避免泄漏实例值) )。然后,您可以将其传递给其他方法,以使其重新填写必填字段。因此,龙目岛不会也不应将这些空检查添加到生成的生成器的不同setter方法中。 (当然,可以扩展lombok,以便用户可以“选择加入”以生成更多的空检查;请参阅this discussion at GitHub。但是,该决定取决于lombok维护者。)

TLDR:从理论上可以发现问题,但是FindBugs不够强大。另一方面,lombok不应添加更多的空检查,因为这会破坏合法的用例。

答案 1 :(得分:4)

这似乎是个不错的选择...

...但请记住,这些都不是:

  • 发现错误
  • Bean验证 JSR303
  • Bean验证2.0 JSR380

发生在编译时,这在本次讨论中非常重要。

Bean验证在运行时发生,因此需要在代码中进行显式调用或在托管环境中进行隐式执行(例如 Spring JavaEE >)通过创建和调用验证器。

FindBugs 是静态字节码分析器,因此会在编译后发生。它使用巧妙的启发式方法,但不执行代码,因此不是100%防水的。 在您的情况下,仅在较浅的情况下进行了可空性检查,并错过了构建器。

请注意,通过手动创建构建器并添加必要的@NotNull注释,如果您未分配任何值,则 FindBugs 将不会生效,与分配{{1 }}。另一个差距是反射和反序列化。

我了解您希望尽快验证以验证批注(如null)表示的合同。

有一种方法可以在@NotNull上进行操作(仍在运行中!),但这有点复杂,需要创建自定义构建器:

也许可以通用以容纳许多类-somoeone请编辑!

SomeClassBuilder.build()