Spring-Boot-3.x-中-NotNull-与-NonNull-的深度解析
Spring Boot 3.x 中 @NotNull 与 @NonNull 的深度解析
在 Java 开发领域,尤其是在 Spring Boot 生态系统中,空指针异常(NPEs)始终是一个顽固的挑战。这些运行时错误可能导致应用程序崩溃、数据不一致以及糟糕的用户体验。为了应对这一问题,Java 社区开发了各种空安全机制,其中注解扮演着至关重要的角色。
本文将深入探讨 Spring Boot 3.x 与 Jakarta 中用于空安全的两个关键注解:
@NotNull
和
@NonNull
。我们将探索它们的起源、用途和实际应用,为您提供编写更健壮和防错代码的知识。
空安全概览
在深入了解具体细节之前,让我们先了解一下背景:
- • 空引用 :由 Tony Hoare 于 1965 年引入,他后来称之为他的“价值十亿美元的错误”。
- • Java 的方法 :与一些具有内置空安全的现代语言不同,Java 依赖于注解和静态分析工具。
- • Spring Boot 3.2 :拥抱 Jakarta EE 9+,带来了包名更改和增强的空安全特性。
注解深度解析
@NotNull
- • 起源 :Jakarta Bean Validation API
- •
包
:
jakarta.validation.constraints.NotNull
- • 用途 :运行时验证,确保值不为 null
- • 行为 :与验证器一起使用时,在运行时触发验证
@NonNull
- • 起源 :Spring Framework
- •
包
:
org.springframework.lang.NonNull
- • 用途 :静态分析和空安全文档
- • 行为 :供 IDE 和静态分析工具使用;无运行时影响
对比分析
让我们分解关键差异:
• 验证机制 :
- •
@NotNull
:主动运行时检查 - •
@NonNull
:被动编译时提示
- •
• 框架集成 :
- •
@NotNull
:广泛认可(Jakarta EE、Spring、Hibernate) - •
@NonNull
:Spring 特有,但受许多 IDE 尊重
- •
• 性能影响 :
- •
@NotNull
:由于验证,运行时开销略有增加 - •
@NonNull
:无运行时影响
- •
• 用例 :
- •
@NotNull
:输入验证,尤其是外部数据 - •
@NonNull
:内部代码约定和 API 文档
- •
• 失败行为 :
- •
@NotNull
:可能抛出ConstraintViolationException
- •
@NonNull
:依赖于代码中的正确 null 检查
- •
综合示例
让我们在 Spring Boot 中使用这两个注解探索一个实际场景。
import jakarta.validation.constraints.NotNull;
import jakarta.validation.Valid;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import java.util.List;
import java.util.Optional;
@Service
@Validated
publicclassProductService {
privatefinal ProductRepository productRepository;
privatefinal PricingService pricingService;
publicProductService(@NotNull ProductRepository productRepository,
@NotNull PricingService pricingService) {
this.productRepository = productRepository;
this.pricingService = pricingService;
}
@NonNull
public Product createProduct(@Valid @NotNull ProductDTO productDTO) {
Productproduct=newProduct(productDTO.getName(), productDTO.getDescription());
product.setPrice(pricingService.calculateInitialPrice(productDTO.getCategory()));
return productRepository.save(product);
}
@NonNull
public Optional<Product> getProductById(@NonNull Long id) {
return productRepository.findById(id);
}
publicvoidupdateProductStock(@NonNull Long id, @NotNull Integer quantity) {
productRepository.findById(id).ifPresent(product -> {
product.setStockQuantity(quantity);
productRepository.save(product);
});
}
@NonNull
public List<Product> searchProducts(@NonNull String keyword) {
return productRepository.searchByNameOrDescription(keyword);
}
}
publicclassProductDTO {
@NotNull
private String name;
@NotNull
private String description;
@NotNull
private String category;
// getters and setters
}
publicclassProduct {
private Long id;
private String name;
private String description;
private Double price;
private Integer stockQuantity;
// constructor, getters, and setters
}
代码解析
构造函数参数 :
public ProductService(@NotNull ProductRepository productRepository, @NotNull PricingService pricingService)
@NotNull
确保 Spring 的依赖注入提供非 null 的依赖项。在 bean 创建期间发生运行时验证。方法返回类型 :
@NonNull public Product createProduct(@Valid @NotNull ProductDTO productDTO)
返回类型上的
@NonNull
向调用者表明此方法永远不会返回 null。静态分析工具可以警告潜在的 null 解引用。方法参数 :
public void updateProductStock(@NonNull Long id, @NotNull Integer quantity)
id
上的@NonNull
用于静态分析。quantity
上的@NotNull
将在运行时验证。DTO 字段 :
public class ProductDTO { @NotNull private String name; // ... }
DTO 字段上的
@NotNull
确保在使用@Valid
时对其进行验证。Optional 的使用 :
@NonNull public Optional<Product> getProductById(@NonNull Long id)
返回
Optional<Product>
是 Java 8+ 处理潜在缺失值的方法。Optional
返回类型上的@NonNull
确保Optional
本身永远不为 null。
最佳实践和专业提示
- •
一致使用
:在 DTO 和实体中,在所有非 null 字段和参数上一致应用
@NotNull
。 - • 验证组 :使用 Jakarta Bean Validation 组在不同上下文中应用不同的验证规则。
- • 自定义约束 :为复杂的验证规则创建自定义约束注解。
- • IDE 中的 Null 分析 :配置您的 IDE(例如,IntelliJ IDEA)以遵循 Spring 的 null 安全注解。
- •
测试
:编写单元测试以验证
@NotNull
约束是否强制执行。 - •
文档
:在公共 API 中使用
@NonNull
,以清晰地向其他开发人员传达 null 安全约定。 - • 性能考虑 :注意关键路径中过度运行时验证的性能影响。
- •
与其他注解结合使用
:将
@NotNull
与其他约束(如@Size
或@Pattern
)结合使用,以进行全面验证。
高级场景
- • 泛型方法中的 Null 安全
- • 响应式编程中的 Null 安全
- • 接口中 Null 性的处理
// 泛型方法中的 Null 安全
@NonNull
public <T> List<T> processItems(@NonNull List<@NotNull T> items) {
// 处理逻辑
}
// 响应式编程中的 Null 安全
@NonNull
public Mono<Product> reactiveCreateProduct(@Valid @NotNull ProductDTO productDTO) {
// 响应式创建逻辑
}
// 接口中 Null 性的处理
publicinterfaceUserService {
@NonNull User findUser(@NonNull String username);
}
为什么在接口中使用 @NonNull?
在我们的接口示例中,我们使用了来自 Spring Framework 的
@NonNull
(org.springframework.lang.NonNull),而不是来自 Jakarta Bean Validation 的
@NotNull
(jakarta.validation.constraints.NotNull)。这种选择是经过深思熟虑的,基于几个重要因素:
- •
语义含义
:
@NonNull
主要用于静态分析和文档。它在不强制运行时行为的情况下传达设计意图。 - •
运行时影响
:
@NotNull
设计用于运行时验证,可能具有性能影响。接口定义约定,而不是实现,因此运行时检查通常不适合此级别。 - •
框架一致性
:Spring Framework 专门为 API 和约定提供了
@NonNull
。在基于 Spring 的应用程序中使用 Spring 的注解可以保持一致性。 - •
编译时检查
:许多 IDE 和静态分析工具都认可
@NonNull
,以便在开发期间进行 null 安全检查。这有助于在开发过程的早期捕获潜在的 null 相关问题。
结论
掌握 Spring Boot 3.2 与 Jakarta 中的
@NotNull
和
@NonNull
注解对于开发健壮、null 安全的应用程序至关重要。虽然
@NotNull
提供运行时验证,但
@NonNull
增强了静态分析和文档。通过明智地应用这些注解,您可以创建强大的防御机制来抵御空指针异常