我有很多使用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);
}
}
我注意到:
@NonNull
,也不会在非null字段中添加任何null检查。SomeObject.set*
方法。我有以下问题:
@NonNull
属性,是否有办法以导致构建时失败的方式使用Lombok构建器(在运行FindBugs时,否则)?答案 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验证在运行时发生,因此需要在代码中进行显式调用或在托管环境中进行隐式执行(例如 Spring 或 JavaEE >)通过创建和调用验证器。
FindBugs 是静态字节码分析器,因此会在编译后发生。它使用巧妙的启发式方法,但不执行代码,因此不是100%防水的。 在您的情况下,仅在较浅的情况下进行了可空性检查,并错过了构建器。
请注意,通过手动创建构建器并添加必要的@NotNull
注释,如果您未分配任何值,则 FindBugs 将不会生效,与分配{{1 }}。另一个差距是反射和反序列化。
我了解您希望尽快验证以验证批注(如null
)表示的合同。
有一种方法可以在@NotNull
上进行操作(仍在运行中!),但这有点复杂,需要创建自定义构建器:
也许可以通用以容纳许多类-somoeone请编辑!
SomeClassBuilder.build()