1. 概述

空指针异常(NullPointerException)是常见的问题。我们可以通过在方法参数上添加注解,如@NotNull,来保护我们的代码。

通过使用@NotNull,我们表示如果想要避免异常,调用方法时参数绝不能为null。然而,仅凭此还不够。让我们深入了解原因。

2. 方法参数上的@NotNull注解

首先,创建一个类,其中包含一个简单返回字符串长度的方法:

接着,我们在参数上添加@NotNull注解:

public class NotNullMethodParameter {
    public int validateNotNull(@NotNull String data) {
        return data.length();
    }
}

当我们导入NotNull时,要注意有多个@NotNull注解的实现。确保它来自正确的包。

我们将使用jakarta.validation.constraints包。

现在,创建一个NotNullMethodParameter,并尝试使用null参数调用我们的方法:

NotNullMethodParameter notNullMethodParameter = new NotNullMethodParameter();
notNullMethodParameter.doesNotValidate(null);

尽管我们有@NotNull注解,但仍然得到了NullPointerException

java.lang.NullPointerException

我们的注解没有效果,因为没有验证器来强制执行它。

3. 添加验证器

因此,我们需要添加Hibernate Validator,它是jakarta.validation的参考实现,以识别@NotNull注解。

<dependency> 
  <groupId>org.hibernate.validator</groupId> 
  <artifactId>hibernate-validator</artifactId> 
  <version>8.0.0.Final</version>
</dependency>

在依赖项配置完成后,我们可以强制执行@NotNull注解。

接下来,使用默认的ValidatorFactory创建一个验证器:

ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();

然后,在带有注解的方法的第一行中进行参数验证:

validator.validate(myString);

现在,当我们使用null参数调用我们的方法时,@NotNull被强制执行:

java.lang.IllegalArgumentException: HV000116: The object to be validated must not be null.

这很好,但是在每个带注解的方法内部都添加验证器调用会导致大量重复代码

4. Spring Boot

幸运的是,对于Spring Boot应用,我们可以采用更简单的方法。

4.1. Spring Boot验证

首先,为Spring Boot的验证添加Maven依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

spring-boot-starter-validation依赖包含了Spring Boot和验证所需的一切。这意味着我们可以移除先前的Hibernate依赖,保持pom.xml干净。

现在,创建一个Spring管理的Component,确保添加@Validated注解。创建一个带有validateNotNull方法的组件,该方法接收一个String参数并返回数据的长度,并在参数上添加@NotNull注解:

@Component
@Validated
public class ValidatingComponent {
    public int validateNotNull(@NotNull String data) {
        return data.length();
    }
}

最后,创建一个带有ValidatingComponent自动注入的SpringBootTest。添加一个测试用例,使用null作为方法参数:

@SpringBootTest
class ValidatingComponentTest {
    @Autowired ValidatingComponent component;

    @Test
    void givenNull_whenValidate_thenConstraintViolationException() {
        assertThrows(ConstraintViolationException.class, () -> component.validate(null));
    }
}

我们得到的ConstraintViolationException显示了参数名称和“必须不为null”的消息:

javax.validation.ConstraintViolationException: validate.data: must not be null

有关方法注解的更多信息,请参阅我们的方法约束文章。

4.2. 一点警示

虽然这适用于我们的公共方法,但如果我们在同一类中的其他未注解方法中调用原始注解方法,会发生什么?

public String callAnnotatedMethod(String data) {
    return validateNotNull(data);
}

NullPointerException依然出现。当从同一类的其他方法中调用带注解的方法时,Spring并不会强制执行NotNull约束。

4. 总结

在这篇文章中,我们学习了如何在标准Java应用程序中的方法参数上使用@NotNull注解。我们还了解了如何使用Spring Boot的@Validated注解简化Spring Bean方法参数验证,同时注意到了其局限性。

如往常一样,本文中展示的所有代码示例均可在GitHub上找到。