Spring Boot Validation

验证表单输入(接口参数)是Web 开发 (API 接口开发)中不可缺少的一个部分。


@NotBlank Validation

首先我们从最简单的不能为空判断开始,介绍 Spring Boot Validation。

假设我们需要提供一个登陆接口,输入用户名密码,返回是否登陆成功。

在去数据库查询用户名密码是否正确之前,我们可以做一些简单的验证。

在 LoginRequest 的 username 和 password 参数上加上 @NotBlank。

package com.example.validation.request;

import javax.validation.constraints.NotBlank;

public class LoginRequest {
    @NotBlank(message = "用户名不能为空")
    private String username;

    @NotBlank(message = "密码不能为空")
    private String password;

    public String getUsername() {
        return username;
    }

    public String getPassword() {
        return password;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

在 UserController::login() 方法接收输入的 loginRequest 前面加上 @Valid 。

package com.example.validation.controllers;

import com.example.validation.request.LoginRequest;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.validation.Valid;

@RestController
@RequestMapping("/user")
public class UserController {
    @PostMapping("/login")
    public LoginRequest login(@RequestBody @Valid LoginRequest loginRequest) {
        return loginRequest;
    }
}

这样当用户输入的用户名或则密码为空的时候,就不会继续进入到下面的业务逻辑,在验证参数的阶段就可以直接返回 400 错误。

Bean Validation

使用 @NotBlank 完成第一个参数验证的例子的时候,会有这样一个问题:@NotBlank 注解是不是 Spring 框架自带的,这样的注解一共有多少个。

这个时候我们就需要了解一个 Bean Validation。

Bean Validation: JSR 303 – JSR 349 – JSR 380

Bean Validation 是一套通过注解来表示对象模型的约束的 Java 规范,最新版本是 Bean Validation 2.0。

定义的内置约束注解有:

Build-in Constraint definitions

Hibernate Validator 是 Bean Validation 规范的实现,除了Bean Validation 定义的 22 种内置约束注解之外,还实现了另外 25 种内置约束注解。

Build-in Constraint definitions

Creating custom constraints

虽然 Hibernate Validator 已经内置了这么多约束注解,但是需求千变万化,总有不能满足的时候,这时候我们就需要定义并实现自己的一套约束注解。

假设我们需要提供一个注册接口,输入用户名、手机号码、邮箱、密码,返回是否注册成功。

除了需要验证用户名密码是否为空之外,还需要验证手机号码和邮箱至少要有一个是必填的。

MinimumRequiredParameters.java

package com.example.validation;

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

@Target({TYPE, ANNOTATION_TYPE})
@Retention(RUNTIME)
@Constraint(validatedBy = MinimumRequiredParametersValidator.class)
@Documented
public @interface MinimumRequiredParameters {

    String message() default "{minimum.required.parameters.message}";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

    String[] value();

    int minimum() default 1;
}

MinimumRequiredParametersValidator.java

package com.example.validation;

import org.springframework.beans.BeanWrapper;
import org.springframework.beans.PropertyAccessorFactory;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

public class MinimumRequiredParametersValidator implements ConstraintValidator<MinimumRequiredParameters, Object> {

    private String[] fields;

    private int minimum;

    private int count;

    @Override
    public void initialize(MinimumRequiredParameters constraintAnnotation) {
        this.fields = constraintAnnotation.value();
        this.minimum = constraintAnnotation.minimum();
        this.count = 0;
    }

    @Override
    public boolean isValid(Object value, ConstraintValidatorContext context) {
        BeanWrapper wrapper = PropertyAccessorFactory.forBeanPropertyAccess(value);

        for (String field: fields) {
            if (wrapper.getPropertyValue(field) != null) { count++; }
        }

        return count >= minimum;
    }
}

resources/ValidationMessages.properties

minimum.required.parameters.message=at least {minimum} of {value} is required

在 RegisterRequest 中使用 @MinimumRequiredParameters 注解判断手机号码和邮箱至少有一个必填。

package com.example.validation.request;

import com.example.validation.MinimumRequiredParameters;

import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;

@MinimumRequiredParameters(value = {"phone", "email"}, message = "手机号码和邮箱至少有一个必填")
public class RegisterRequest {
    @NotBlank(message = "用户名不能为空")
    private String username;

    @Pattern(regexp = "\\d{11}", message = "手机号码格式不正确")
    private String phone;

    @Email(message = "邮箱格式不正确")
    private String email;

    @NotBlank(message = "密码不能为空")
    private String password;

    public String getUsername() {
        return username;
    }

    public String getPhone() {
        return phone;
    }

    public String getEmail() {
        return email;
    }

    public String getPassword() {
        return password;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

参考:

https://spring.io/guides/gs/validating-form-input/

https://beanvalidation.org

https://beanvalidation.org/2.0/spec/#builtinconstraints

http://hibernate.org/validator/

https://docs.jboss.org/hibernate/stable/validator/reference/en-US/html_single/#validator-customconstraints

https://stackoverflow.com/questions/9284450/jsr-303-validation-if-one-field-equals-something-then-these-other-fields-sho

 409 total views,  1 views today

Leave a Reply

Your email address will not be published. Required fields are marked *