使用 Spring Validator 接口进行数据校验

1、概览

Spring Validator 接口为验证对象提供了一种灵活且可定制的方式。本文将带你了解如何在 Spring 应用中使用 Validator 接口验证对象。

2、Spring Validator 接口

Validator 接口是 Spring 框架的一部分,它提供了一种验证对象的方法。

这是一个简单的接口,定义了两个方法:supports()validate(),用于确定 Validator 是否能验证对象并执行验证逻辑。

2.1、supports(Class clazz) 方法

Validator 接口中的 supports() 方法决定 Validator 能否验证特定类的实例。该方法的参数 Class<?> clazz 代表被验证对象的类。它是一个泛型类(Class<?>),可以灵活用于不同的对象类型。

具体来说,Spring 利用 isAssignableFrom() 方法来检查对象是否可以合法地转换为 Validator 支持类的对象。因此,如果 Validator 能处理所提供的 clazz 对象,它就会返回 true,否则就会返回 false,以表示应使用另一个 Validator

@Override
public boolean supports(Class<?> clazz) {
    return User.class.isAssignableFrom(clazz);
}

在上例中,Validator 配置为仅支持验证类型为 User 或其子类的对象。isAssignableFrom() 方法通过继来承验证兼容性 - 对于 User 及其子类,它返回 true,对于任何其他类类型,它返回 false

2.2、validate(Object target, Errors errors) 方法

validate() 方法则用于为 Validator 支持的对象定义自定义验证逻辑。

该方法接收两个关键参数:

  • Object target:该参数代表要验证的实际对象。Spring MVC 会自动将要验证的对象传递给此方法。
  • Errors:该参数是 Errors 接口的一个实例。它提供了各种方法,用于添加对象验证错误的信息。

下面是一个 validate() 方法的示例:

@Override
public void validate(Object target, Errors errors) {
    User user = (User) target;
    if (StringUtils.isEmpty(user.getName())) { 
        errors.rejectValue("name", "name.required", "Name cannot be empty"); 
    }
}

在这个示例中,validate() 方法对 User 对象执行各种验证,并使用 rejectValue() 将特定的错误信息添加到 Errors 对象中,以处理目标字段的错误。值得注意的是,rejectValue() 方法需要三个主要参数:

  • field:出错字段的名称,如 name(姓名)。
  • errorCode:可识别错误的唯一代码,如 name.required(名称必填)。
  • defaultMessage:默认错误信息,在找不到其他信息时显示,例如 “名称不能为空”。

3、实现 Validator

要创建一个 Validator,我们需要实现 Validator 接口。下面是一个验证 User 对象的简单 Validator 示例:

public class UserValidator implements Validator {
    @Override
    public boolean supports(Class<?> clazz) {
        return User.class.isAssignableFrom(clazz);
    }

    @Override
    public void validate(Object target, Errors errors) {
        User user = (User) target;
        if (StringUtils.isEmpty(user.getName())) {
            errors.rejectValue("name", "name.required", "Name cannot be empty");
        }
        if (StringUtils.isEmpty(user.getEmail())) {
            errors.rejectValue("email", "email.required", "Invalid email format");
        }
    }
}

3.1、创建 User 类

定义要验证的对象,以 User 类为例:

public class User {
    private String name;
    private String email;

    // Get/Set 方法省略
}

3.2、配置 Spring Bean

接下来,为了将自定义 Validator 集成到基于 Spring 的应用中,我们可以使用 Spring 配置(Configuration)类将其注册为 Application Context 中的一个 Bean。这种注册可确保 Validator 在整个应用生命周期中都可用于依赖注入(DI):

@Configuration
public class AppConfig implements WebMvcConfigurer{
    @Bean
    public UserValidator userValidator() {
        return new UserValidator();
    }
}

通过在 userValidator() 方法上使用 @Bean 注解,可以确保它返回的对象会被 Spring 注册为 Application Context 中的一个 Bean。

3.3、在 Spring MVC Controller 中整合 Validator

Validator 注册后,就可以用它来验证 Spring MVC Controller 中的 User 对象。

接下来,创建一个 UserController 来处理与用户(User)相关的请求:

@RestController
@RequestMapping("/api/users")
public class UserController {
    @Autowired
    private UserValidator userValidator;

    @PostMapping
    public ResponseEntity<?> createUser(@RequestBody User user) {
        Errors errors = new BeanPropertyBindingResult(user, "user");
        userValidator.validate(user, errors);
        if (errors.hasErrors()) {
            return ResponseEntity.badRequest().body(errors.getAllErrors());
        }
        // TODO 保存对象到数据库
        return ResponseEntity.ok("User created successfully!");
    }
}

在上例中,使用 Spring 的 @RestController 注解来表示该 Controller 返回 JSON 响应。此外,还使用 @RequestBody 将传入的 JSON 请求体与 User 对象绑定。如果验证失败,就返回一个 400 Bad Request 响应,其中的 JSON 响应体包含错误信息。否则,返回一个 200 OK 的成功响应。

4、使用 cURL 进行测试

使用 curl 来测试此 API。

发送包含 User 对象数据的 JSON 请求体:

curl -X POST \
  http://localhost:8080/api/users \
  -H 'Content-Type: application/json' \
  -d '{"name":"","email":""}'

这会返回一个 400 Bad Request 响应,其 JSON 响应体包含错误信息:

[
  {
    "codes": [
      "name.required.user.name",
      "name.required.name",
      "name.required.java.lang.String",
      "name.required"
    ],
    "arguments": null,
    "defaultMessage": "Name cannot be empty",
    "objectName": "user",
    "field": "name",
    "rejectedValue": "",
    "bindingFailure": false,
    "code": "name.required"
  },
  {
    "codes": [
      "email.required.user.email",
      "email.required.email",
      "email.required.java.lang.String",
      "email.required"
    ],
    "arguments": null,
    "defaultMessage": "Invalid email format",
    "objectName": "user",
    "field": "email",
    "rejectedValue": "",
    "bindingFailure": false,
    "code": "email.required"
  }
]

如果发送一个包含 nameemail 的有效 User 对象,API 就会返回 200 OK 响应和成功信息:

curl -X POST \
  http://localhost:8080/api/users \
  -H 'Content-Type: application/json' \
  -d '{"name":"John Doe","email":"johndoe@example.com"}'

成功的响应信息如下:

"User created successfully!"

5、Validation Context

此外,在某些情况下,我们可能希望向 Validator 传递额外的上下文。

Spring Validator 接口通过 validate(Object target, Errors errors, Object... validationHints) 方法支 Validation Context。因此,可以在调用 validate() 方法时传递其他对象作为验证提示。

例如,根据特定场景验证 User 对象:

public void validate(Object target, Errors errors, Object... validationHints) {
    User user = (User) target;
    if (validationHints.length > 0) {
        if (validationHints[0] == "create") {
            if (StringUtils.isEmpty(user.getName())) {
                errors.rejectValue("name", "name.required", "Name cannot be empty");
            }
            if (StringUtils.isEmpty(user.getEmail())) {
                errors.rejectValue("email", "email.required", "Invalid email format");
            }
        } else if (validationHints[0] == "update") {
            // 执行特定的更新验证
            if (StringUtils.isEmpty(user.getName()) && StringUtils.isEmpty(user.getEmail())) {
                errors.rejectValue("name", "name.or.email.required", "Name or email cannot be empty");
            }
        }
    } else {
        // 执行默认验证
    }
}

在上例中,UserValidator 会检查 validationHints 数组,以确定使用哪种验证方案。

更新 UserController 以使用带有 validationHintsUserValidator

@PutMapping("/{id}")
public ResponseEntity<?> updateUser(@PathVariable Long id, @RequestBody User user) {
    Errors errors = new BeanPropertyBindingResult(user, "user");
    userValidator.validate(user, errors, "update");
    if (errors.hasErrors()) {
        return ResponseEntity.badRequest().body(errors.getAllErrors());
    }
    // TODO 更新数据库中的用户对象
    return ResponseEntity.ok("User updated successfully!");
}

现在,执行下面的 curl 命令,nameemail 字段均为空:

curl -X PUT \
  http://localhost:8080/api/users/1 \
  -H 'Content-Type: application/json' \
  -d '{"name":"","email":""}'

UserValidator 会返回带有错误信息的 400 Bad Request 响应:

[
  {
    "codes": [
      "name.or.email.required.user.name",
      "name.or.email.required.name",
      "name.or.email.required.java.lang.String",
      "name.or.email.required"
    ],
    "arguments": null,
    "defaultMessage": "Name or email cannot be empty",
    "objectName": "user",
    "field": "name",
    "rejectedValue": "",
    "bindingFailure": false,
    "code": "name.or.email.required"
  }
]

如果只传递其中一个字段,例如 name 字段,那么 UserValidator 就会允许继续进行更新:

curl -X PUT \
  http://localhost:8080/api/users/1 \
  -H 'Content-Type: application/json' \
  -d '{"name":"John Doe"}'

响应是 200 OK,表明更新成功:

"User updated successfully!"

6、总结

本文介绍了如何在 Spring 应用中使用 Validator 接口来验证对象,主要介绍了 Validator 接口的两个方法:supports()validate(),以及如何实现自定义 Validator 来验证对象。


Ref:https://www.baeldung.com/spring-validator-interface