Spring MVC 自定义验证器

x33g5p2x  于2021-10-21 转载在 Java  
字(7.6k)|赞(0)|评价(0)|浏览(418)

Spring MVC 自定义验证器

在这篇文章中,我们将学习如何使用 Spring MVC 自定义验证器。我们将探讨如何创建和使用您自己的自定义验证器。

介绍

Spring MVCJSR-303 Bean Validation API 提供第一手支持。我们可以使用这个 API 来验证我们的输入数据。 Bean Validation API 附带了许多不同的验证器,可以通过使用简单的注释来使用,但是如果我们需要根据自定义业务规则验证数据,我们可以选择创建我们自己的自定义验证器并将其挂钩使用 *Spring MVC 验证框架 *。

我们将探索 Spring MVC Custom Validator 并了解如何将其挂接到我们的应用程序中。

1. Maven 设置

为了创建和使用自定义验证器,我们需要在我们的类路径中验证 API 和 JSR-303 合规性验证 API,我们将在我们的示例中使用 Hibernate 验证器。首先,我们需要在 pom.xml 文件中添加以下条目

<dependency>
    <groupId>javax.validation</groupId>
    <artifactId>validation-api</artifactId>
    <version>1.1.0.Final</version>
</dependency>
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>5.4.1.Final</version>
</dependency>

我们将在这篇文章中使用 Spring Boot,因此我们将使用 Spring Boot starters 在我们的类路径中添加验证 API。要在 Spring Boot 应用程序中使用 Hibernate 验证 API,您需要在 pom.xml 文件中添加以下启动器

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

我们知道,使用 Spring Boot 时不需要指定版本,因为它会由 Spring Boot 自动处理。

2. 自定义类级别验证

为了理解它,让我们举一个简单的例子,我们要创建一个自定义验证器来验证输入表单中的两个字段,以确保它们相等。 (假设我们要确认客户的电子邮件地址)

2.1 注释

我们将首先创建一个新的 @interface 来定义我们新的自定义约束。

@Constraint(validatedBy = FieldMatchValidator.class)
@Documented
@Target({TYPE, ANNOTATION_TYPE})
@Retention(RUNTIME)
public @interface FieldMatch {
    String message() default "Fields are not matching";

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

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

    /**
     * @return The first field
     */
    String first();

    /**
     * @return The second field
     */
    String second();

    /**
     * Defines several <code>@FieldMatch</code> annotations on the same element
     *
     * @see FieldMatch
     */
    @Target({TYPE, ANNOTATION_TYPE})
    @Retention(RUNTIME)
    @Documented
    @interface List {
        FieldMatch[] value();
    }
}

使用 @Constraint 注释,我们指定哪个类将实际执行验证,在我们的例子中,FieldMatchValidator 类将进行实际验证,message() 将定义将要执行的消息当验证失败时向客户显示,这是默认消息,可以通过配置进行更改(我们很快就会看到)。

2.2 验证类

让我们看看实际的验证类,它将负责执行我们的自定义验证。

public class FieldMatchValidator implements ConstraintValidator<FieldMatch, Object> {


    private String firstFieldName;
    private String secondFieldName;

    public void initialize(final FieldMatch constraintAnnotation) {
        this.firstFieldName = constraintAnnotation.first();
        this.secondFieldName = constraintAnnotation.second();
    }

    public boolean isValid(final Object value, final ConstraintValidatorContext context) {
        try {
            final Object firstObj = PropertyUtils.getProperty(value, this.firstFieldName);
            final Object secondObj = PropertyUtils.getProperty(value, this.secondFieldName);
            return firstObj == null && secondObj == null || firstObj != null && firstObj.equals(secondObj);
        } catch (final Exception ex) {
            //LOG.info("Error while getting values from object", ex);
            return false;
        }

    }
}

我们的 Validation 类实现了 ConstraintValidator 接口并且应该实现 isValid 方法,我们所有的验证逻辑都将在这个方法中。在我们的自定义验证中,我们从两个字段中获取值并匹配它们以返回一个布尔值。

3. 验证在行动

是时候看看我们的自定义验证在行动了,为了演示它,我们将使用一个简单的基于 Spring Boot 的 Web 应用程序,我们将验证 2 个字段,并在两个字段彼此不匹配的情况下抛出错误。

3.1 示例控制器
@Controller
public class CustomValidatorController {

    @RequestMapping("/custom-validator")
    public String hello(Person person) {
        return "customValidator";
    }

    @PostMapping("/custom-validator")
    public String demoValidation(@Valid Person person, BindingResult bindingResult) {

        if (bindingResult.hasErrors()) {

            return "customValidator";
        }

        return "redirect:/results";
    }

    @RequestMapping("/success")
    public String welcome() {
        return "welcome";
    }
}
3.2 人对象
@FieldMatch(first = "email", second = "confirmEmail", message = "Email does not match")
public class Person {

    @NotNull
    private String name;
    private String email;
    private String confirmEmail;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getEmail() {
        return email;
    }

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

    public String getConfirmEmail() {
        return confirmEmail;
    }

    public void setConfirmEmail(String confirmEmail) {
        this.confirmEmail = confirmEmail;
    }
}

如您所见,我们在 Person 类的顶部添加了 @FieldMatch 注释,它将比较 emailconfirmEmail 字段,如果它们不匹配,它将抛出验证错误。

3.3 查看
<html>
<body>
<form action="#" th:action="@{/custom-validator}" th:object="${person}" method="post">
    <table>
        <p th:if="${#fields.hasErrors('global')}" th:errors="*{global}">
            Incorrect date
        </p>
        <tr>
            <td>Name:</td>
            <td><input type="text" th:field="*{name}" /></td>
            <td th:if="${#fields.hasErrors('name')}" th:errors="*{name}">Name Error</td>
        </tr>
        <tr>
            <td>Email:</td>
            <td><input type="text" th:field="*{email}" /></td>
            <td th:if="${#fields.hasErrors('email')}" th:errors="*{email}">Email Error</td>
        </tr>
        <tr>
            <td>Confirm Email:</td>
            <td><input type="text" th:field="*{confirmEmail}" /></td>
            <td th:if="${#fields.hasErrors('confirmEmail')}" th:errors="*{confirmEmail}">Email Error</td>
        </tr>
        <tr>
            <td><button type="submit">Submit</button></td>
        </tr>
    </table>
</form>
</body>
</html>

如果我们运行我们的应用程序并添加不匹配的电子邮件 ID,我们将在前端收到“电子邮件不匹配”错误。

我们还可以在字段级别定义自定义验证。我们需要使用相同的方法来定义字段级注释。

4. 自定义字段级别验证

我们还可以在字段级别定义自定义验证。我们需要使用类似的方法来定义字段级别的注释。

4.1 Annotation
@Constraint(validatedBy = CountryMatchValidator.class)
@Documented
@Target( { ElementType.METHOD, ElementType.FIELD })
@Retention(RUNTIME)
public @interface CountryMatch {
    String message() default "Invalid Country";

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

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

    /**
     * Defines several <code>@FieldMatch</code> annotations on the same element
     *
     * @see FieldMatch
     */
    @Target({TYPE, ANNOTATION_TYPE})
    @Retention(RUNTIME)
    @Documented
    @interface List {
        FieldMatch[] value();
    }
}
4.2 Validation类
public class CountryMatchValidator implements ConstraintValidator<CountryMatch, String> {


    /**
     * Initializes the validator in preparation for
     * {@link #isValid(Object, ConstraintValidatorContext)} calls.
     * The constraint annotation for a given constraint declaration
     * is passed.
     * <p/>
     * This method is guaranteed to be called before any use of this instance for
     * validation.
     *
     * @param constraintAnnotation annotation instance for a given constraint declaration
     */
    @Override
    public void initialize(CountryMatch constraintAnnotation) {

    }

    /**
     * Implements the validation logic.
     * The state of {@code value} must not be altered.
     * <p/>
     * This method can be accessed concurrently, thread-safety must be ensured
     * by the implementation.
     *
     * @param country  country
     * @param context context in which the constraint is evaluated
     * @return {@code false} if {@code value} does not pass the constraint
     */
    @Override
    public boolean isValid(String country, ConstraintValidatorContext context) {
        try {
            return country == null && country.equals("US");
        } catch (final Exception ex) {
            //LOG.info("Error while getting values from object", ex);
            return false;
        }
    }
}
4.3 Person对象
@FieldMatch(first = "email", second = "confirmEmail", message = "Email does not match")
public class Person {

    @NotNull
    private String name;
    private String email;
    private String confirmEmail;

    @CountryMatch(message = "Country should be US")
    private String country;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getEmail() {
        return email;
    }

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

    public String getConfirmEmail() {
        return confirmEmail;
    }

    public void setConfirmEmail(String confirmEmail) {
        this.confirmEmail = confirmEmail;
    }

    public String getCountry() {
        return country;
    }

    public void setCountry(String country) {
        this.country = country;
    }
}

为了启动 Spring MVC JSR 验证,您需要在控制器中使用 @Valid 注释来注释模型

5. 总结

在这篇文章中,我们学习了如何使用 Spring MVC 自定义验证器。我们介绍了如何创建和使用类和字段级验证。

本文的所有代码都可用Over on Github。这是一个基于 Maven 的项目。

相关文章