前言
首先呢,先祝大家新年快乐!!牛年大吉!!
我们现在开启我们新一年的学习吧。今天,我们将聊一下在Springboot应用程序中验证数据的常用实现。
Hibernate验证器
一般实现是通过使用Bean验证API进行验证。Bean验证API的参考实现是Hibernate验证器。
所有必需的依赖项都打包在springbootstarter POM springbootstarter验证中。因此,通常您只需要开始以下依赖关系:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency>
验证约束是通过使用适当的Bean验证注解对字段进行注解来定义的。例如:
public class Address { @NotBlank @Size(max = 50) private String street; @NotBlank @Size(max = 50) private String city; @NotBlank @Size(max = 10) private String zipCode; @NotBlank @Size(max = 3) private String countryCOde; // getters + setters }
对于这些注解的作用是显而易见的。我们将在下面的许多示例中使用到这个Address类。
可以在Bean验证文档中找到内置约束注解的完整列表。
如果有需要,通过创建自定义约束验证器来定义自己的验证约束。
请求数据验证
在使用Springboot构建RestAPI接口时,大多时候需要验证传入的请求数据。这可以通过简单地将@Valid注解添加到@RequestBody方法参数来完成。
例如:
@RestController public class AddressController { @PostMapping("/address") public void createAddress(@Valid @RequestBody Address address) { // .. } }
通过@Valid注解,就开启了数据约束验证。
Spring现在根据先前定义的约束自动验证传递的Address对象。
这种类型的验证通常用于确保客户端发送的数据语法正确。如果验证失败,则不调用控制器方法,并向客户端返回HTTP 400(错误请求)响应。更复杂的特定于业务的验证约束通常应该稍后在业务层中检查。
持久层数据验证
在Springboot应用程序中使用关系数据库时,持久层采用了Hibernate框架,也会支持验证。Hibernate支持Bean验证。如果实体包含Bean验证注解,则在持久化实体时会自动检查这些注解。
请注意,持久层绝对不应该是验证的唯一位置。如果验证在这里失败,通常意味着其他应用程序组件中缺少某种验证。持久层验证应该被视为最后一道防线。除此之外,持久性层对于与业务相关的验证来说通常为时已晚。
方法参数验证
Spring提供了对于方法参数的数据约束验证。通过向方法参数添加Bean验证注解。然后,Spring使用AOP拦截器在调用实际方法之前验证参数。
例如:
@Service @Validated public class CustomerService { public void updateAddress( @Pattern(regexp = "\\w{2}\\d{8}") String customerId, @Valid Address newAddress ) { // .. } }
另外,这种方法对于验证进入服务层的数据非常有用。但是,在使用这种方法之前,应该了解它的局限性,因为这种类型的验证只有在涉及Spring代理时才有效。
同时需要注意,这种方法会使单元测试变得更困难。
编程方式触发Bean验证
在上述的验证方案中,实际的验证是由Spring或Hibernate触发的。很多时候,我们需要根据合适时机,灵活触发Bean验证。
下面,我们尝试用编程方式,触发对于Bean验证。
我们首先创建一个验证Facade bean:
@Component public class ValidationFacade { private final Validator validator; public ValidationFacade(Validator validator) { this.validator = validator; } public <T> void validate(T object, Class<?>... groups) { Set<ConstraintViolation<T>> violations = validator.validate(object, groups); if (!violations.isEmpty()) { throw new ConstraintViolationException(violations); } } }
这个bean接受一个验证器作为有参构造函数。验证器是Bean验证API的一部分,负责验证Java对象。
在validate(..)方法中,我们使用验证器来验证传递的对象。结果是一组约束冲突。如果未违反任何验证约束(=对象有效),则集合为空。否则,我们抛出一个约束冲突异常。
我们现在可以将我们的验证门面注入到其他bean中。例如:
@Service public class CustomerService { private final ValidationFacade validationFacade; public CustomerService(ValidationFacade validationFacade) { this.validationFacade = validationFacade; } public void updateAddress(String customerId, Address newAddress) { validationFacade.validate(newAddress); // ... } }
为了验证一个对象Address(这里是新地址),我们只需调用validate(..)方法。当然,我们也可以将验证器直接注入到我们的客户服务中。但是,在验证错误的情况下,我们通常不希望处理返回的约束冲突集。相反,我们可能只想抛出一个异常,这正是验证门面所做的。
通常,这是在服务/业务层进行验证的好的一种实现。它不仅限于方法参数,而且可以用于不同类型的对象。例如,我们可以从数据库中加载一个对象,修改它,然后在继续之前验证它。
这种方法对于单元测试也很好,因为我们可以简单地模拟验证门面。如果我们想在单元测试中进行真正的验证,可以手动创建所需的验证器实例。
验证内部业务类
我们可以对实际的业务Bean对象进行创建时的动态验证。
在DDD领域驱动设计开发时,采用动态验证比较重要。例如,在创建地址实例时,构造函数可以确保我们不能构造无效的对象:
public class Address { @NotBlank @Size(max = 50) private String street; @NotBlank @Size(max = 50) private String city; ... public Address(String street, String city) { this.street = street; this.city = city; ValidationHelper.validate(this); } }
在这里,构造函数调用静态validate(..)方法来验证对象状态。这个静态validate(..)方法与前面在ValidationFacade中显示的方法类似:
这里的区别是,我们没有通过Spring检索验证程序实例。相反,我们使用以下方法手动创建:
Validation.buildDefaultValidatorFactory().getValidator()
通过这种方式,我们可以直接将验证集成到域对象中,而无需依赖外部人员来验证对象。
总结
今天我们聊了下,在Springboot应用程序中处理验证的不同方法。
不同的使用,根据具体的业务需要,我们实际去做自己的开发策略。
好了,今天聊天结束。新的一年,大家也要好好学习哦!