输入验证是Spring处理的最重要Web开发任务之一。在Spring MVC中,有两种方式可以验证输入,即利用Spring自带的验证框架,或者利用JSR 303实现。本篇博客将介绍这两种输入验证方法。

本篇博客用两个不同的示例分别介绍这两种方式:spring-validator和jsr303-validator。

一 验证概览

Converter和Formatter作用于字段级。在MVC Web应用中,它们将String类型转换或格式化成另一种Java类型,如java.time.LocalDate。验证器则作用于对象级。它决定某一个对象中的所有字段是否均是有效的,以及是否遵循某些规则。一个典型的Spring MVC应用会同时应用到Formatter(或Converter)和Validator。

如果一个应用程序既使用了Formatter,又有了Validator,那么,应用中的事件顺序是这样的:在调用Controller的请求处理方式时,将会有一个或者多个Formatter,试图将输入字符串转换成domain对象中的属性(或者说字段)值,一旦格式化成功,验证器就会介入。

例如:Order对象有一个shippingDate属性(其类型为LocalDate),它的值绝对不可能早于今天的日期。当调用OrderController时,LocalDateFormatter会将字符串转换成LocalDate,并将它赋予Order对象的shippingDate属性。如果转换失败,用户就会被转回到前一个表单;如果转换成功,则会调用验证器,查看shippingDate是否早于今天的日期。

现在,你或许会问,将验证逻辑转移到LocalDateFormatter中是否更加明智?

因为比较一下日期并非难事,但答案却是肯定的。首先,LocalDateFormatter还可以用于将其它字符串格式化成日期,如birthDate或者purchaseDate。这两个日期的规则都不同于shippingDate,事实上,比如,员工的出生日期绝对不可能晚于今日。

其次,校验器可以检查两个或更多字段之间的关系,各字段均受不同的Formatter支持。例如,假设Employee对象有birthDate属性和startDate属性,验证器就可以设定规则,使任何员工的入职日期均不可能早于他的出生日期。因此,有效的Employee对象必须让它的birthDate属性值早于其startDate值,这就是验证器的任务。

二 Spring验证器

从一开始,Spring就设计了输入验证,甚至早于JSR 303(Java验证规范)。因此,Spring的Validation框架至今都很普遍,对于新项目,一般建议使用JSR 303验证器。

为了创建Spring验证器,要实现org.springframework.validation.Validator接口,这个接口的源码如下:

/*
 * Copyright 2002-2018 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.validation;

/**
 * A validator for application-specific objects.
 *
 * <p>This interface is totally divorced from any infrastructure
 * or context; that is to say it is not coupled to validating
 * only objects in the web tier, the data-access tier, or the
 * whatever-tier. As such it is amenable to being used in any layer
 * of an application, and supports the encapsulation of validation
 * logic as a first-class citizen in its own right.
 *
 * <p>Find below a simple but complete {@code Validator}
 * implementation, which validates that the various {@link String}
 * properties of a {@code UserLogin} instance are not empty
 * (that is they are not {@code null} and do not consist
 * wholly of whitespace), and that any password that is present is
 * at least {@code 'MINIMUM_PASSWORD_LENGTH'} characters in length.
 *
 * <pre class="code"> public class UserLoginValidator implements Validator {
 *
 *    private static final int MINIMUM_PASSWORD_LENGTH = 6;
 *
 *    public boolean supports(Class clazz) {
 *       return UserLogin.class.isAssignableFrom(clazz);
 *    }
 *
 *    public void validate(Object target, Errors errors) {
 *       ValidationUtils.rejectIfEmptyOrWhitespace(errors, "userName", "field.required");
 *       ValidationUtils.rejectIfEmptyOrWhitespace(errors, "password", "field.required");
 *       UserLogin login = (UserLogin) target;
 *       if (login.getPassword() != null
 *             && login.getPassword().trim().length() < MINIMUM_PASSWORD_LENGTH) {
 *          errors.rejectValue("password", "field.min.length",
 *                new Object[]{Integer.valueOf(MINIMUM_PASSWORD_LENGTH)},
 *                "The password must be at least [" + MINIMUM_PASSWORD_LENGTH + "] characters in length.");
 *       }
 *    }
 * }</pre>
 *
 * <p>See also the Spring reference manual for a fuller discussion of
 * the {@code Validator} interface and its role in an enterprise
 * application.
 *
 * @author Rod Johnson
 * @see SmartValidator
 * @see Errors
 * @see ValidationUtils
 */
public interface Validator {

    /**
     * Can this {@link Validator} {@link #validate(Object, Errors) validate}
     * instances of the supplied {@code clazz}?
     * <p>This method is <i>typically</i> implemented like so:
     * <pre class="code">return Foo.class.isAssignableFrom(clazz);</pre>
     * (Where {@code Foo} is the class (or superclass) of the actual
     * object instance that is to be {@link #validate(Object, Errors) validated}.)
     * @param clazz the {@link Class} that this {@link Validator} is
     * being asked if it can {@link #validate(Object, Errors) validate}
     * @return {@code true} if this {@link Validator} can indeed
     * {@link #validate(Object, Errors) validate} instances of the
     * supplied {@code clazz}
     */
    boolean supports(Class<?> clazz);

    /**
     * Validate the supplied {@code target} object, which must be
     * of a {@link Class} for which the {@link #supports(Class)} method
     * typically has (or would) return {@code true}.
     * <p>The supplied {@link Errors errors} instance can be used to report
     * any resulting validation errors.
     * @param target the object that is to be validated
     * @param errors contextual state about the validation process
     * @see ValidationUtils
     */
    void validate(Object target, Errors errors);

}
View Code

相关文章: