Hay ventajas y desventajas de tratar la validación como lógica de negocios, y Spring ofrece reglas de diseño para la validación (y el enlace de datos) que no excluyen ninguna de las dos. En particular, la validación no debe estar vinculada a la capa web, debe ser fácilmente localizable y debe ser posible conectar cualquier validador disponible. Dados estos requisitos, Spring proporciona un contrato Validator, que es básico y se puede utilizar en todos los niveles de la aplicación.

El enlace de datos es útil para garantizar que la entrada del usuario esté vinculada dinámicamente. al modelo de dominio de la aplicación (o cualquier objeto que se utilice para procesar la entrada del usuario). Spring proporciona un DataBinder bien formulado para realizar exactamente esta tarea. Validator y DataBinder forman el paquete validation, que se utiliza principalmente en el nivel web, pero no se limita a él.

BeanWrapper es un concepto fundamental en Spring Framework y se utiliza en muchos casos. Sin embargo, probablemente no necesitarás usar BeanWrapper directamente. Sin embargo, dado que se trata de documentación de referencia, consideramos necesario proporcionar algunas aclaraciones. Explicamos BeanWrapper en este capítulo porque, si vas a usarlo, lo más probable es que lo hagas cuando intentes vincular datos a objetos.

Spring's DataBinder y BeanWrapper de nivel inferior utilizan la implementación PropertyEditorSupport para analizar y dar formato a los valores de propiedad. Los tipos PropertyEditor y PropertyEditorSupport son parte de la especificación de clase JavaBeans y también se describen en este capítulo. Spring 3 introdujo el paquete core.convert, que proporciona funciones generales de conversión de tipos, así como un paquete de "formato" de alto nivel para formatear los valores de los campos de la interfaz de usuario. Puede utilizar estos paquetes como alternativas más sencillas para implementar PropertyEditorSupport. También se describen en este capítulo.

Spring admite la validación de Java Bean a través de un marco personalizado y un adaptador para el contrato Validator propio de Spring. Las aplicaciones pueden habilitar la validación de beans globalmente una vez y utilizarla únicamente para las necesidades de validación. En el nivel web, las aplicaciones pueden registrar opcionalmente instancias locales de Validator de Spring para DataBinder, lo que puede resultar útil para conectar una lógica de validación personalizada.

Validación uso de la interfaz de validación de Spring

Spring ofrece una interfaz Validator que se puede utilizar para validar objetos. La interfaz Validator funciona utilizando el objeto Errors, por lo que durante las comprobaciones de validación, los validadores pueden informar errores de validación al objeto Errors.

Considere el siguiente ejemplo de un pequeño objeto de datos:

Java

public class Person {
    private String name;
    private int age;
    // regular getters and setters...
}
Kotlin
class Person(val name: String, val age: Int)

El siguiente ejemplo proporciona la lógica de validación para la clase Person mediante la implementación de los dos métodos siguientes de interface org.springframework.validation.Validator:

  • supports(Class): si este Validator ¿Puede validar instancias de la Clase proporcionada?

  • validate(Object, org.springframework.validation.Errors): Valida el objeto dado y, si detecta errores, los registra en el objeto Errors dado.

La implementación del Validator es bastante simple, especialmente si conoce la clase auxiliar ValidationUtils, que también proporciona Spring Framework. El siguiente ejemplo implementa un Validador para instancias de Person:

Java

public class PersonValidator implements Validator {
    /* *
    * This validator only validates Person instances.
    */
    public boolean supports(Class clazz) {
        return Person.class.equals(clazz);
    }
    public void validate(Object obj, Errors e) {
        ValidationUtils.rejectIfEmpty(e, "name", "name.empty");
        Person p = (Person) obj;
        if (p.getAge() < 0) {
            e.rejectValue("age", "negativevalue");
        } else if (p.getAge() > 110) {
                e.rejectValue("age", "too.darn.old");
        }
    }
}
Kotlin

class PersonValidator : Validator {
    /**
     * This validator only validates Person instances.
     */
     override fun supports(clazz: Class<*>): Boolean {
         return Person::class.java == clazz
     }
     override fun validate(obj: Any, e: Errors) {
         ValidationUtils.rejectIfEmpty(e, "name" , "name.empty")
         val p = obj as Person
         if (p.age < 0) {
             e.rejectValue("age", "negativevalue")
         } else if (p.age > 110) {
             e.rejectValue ("age", "too.darn.old")
         }
     }
}

static rechazarIfEmpty(..) método de la clase ValidationUtils se usa para excluir la propiedad name si es null o la cadena vacía. Vea el javadoc en ValidationUtils para ver qué funciones proporciona más allá de las mostradas en el ejemplo anterior.

Por supuesto, puedes implementar una única clase Validator para validar cada uno de los objetos anidados en un objeto completamente funcional, pero puede ser mejor encapsular la lógica de validación para cada clase de objeto anidado en su propia implementación Validator. Un ejemplo simple de un objeto "con todas las funciones" sería un Cliente, que consta de dos propiedades String (el primer y segundo nombre) y una compleja Address Objeto. Los objetos Address se pueden utilizar independientemente de los objetos Customer, por lo que se ha implementado un AddressValidator independiente. Si desea que su CustomerValidator reutilice la lógica contenida en la clase AddressValidator sin recurrir a copiar y pegar, puede inyectar una dependencia en el AddressValidator. o cree una instancia dentro de su CustomerValidator como se muestra en el siguiente ejemplo:

Java

public class CustomerValidator implements Validator {
    private final Validator addressValidator;
    public CustomerValidator(Validator addressValidator) {
        if (addressValidator == null) {
            throw new IllegalArgumentException("The supplied [Validator] is " +
                "required and must not be null.");
        }
        if (!addressValidator.supports(Address.class)) {
            throw new IllegalArgumentException("The supplied [Validator] must " +
                "support the validation of [Address] instances.");
        }
        this.addressValidator = addressValidator;
    }
    /**
    * This validator validates instances of Customer as well as any subclasses of Customer.
    */
    public boolean supports(Class clazz) {
        return Customer.class.isAssignableFrom(clazz);
    }
    public void validate(Object target, Errors errors) {
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "firstName", "field.required");
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "surname", "field.required");
        Customer customer = (Customer) target;
        try {
            errors.pushNestedPath("address");
            ValidationUtils.invokeValidator(this.addressValidator, customer.getAddress(), errors);
        } finally {
            errors.popNestedPath();
        }
    }
}
Kotlin

class CustomerValidator(private val addressValidator: Validator) : Validator {
    init {
         if ( addressValidator == null) {
             throw IllegalArgumentException("The supplied [Validator] is required and must not be null.")
         }
         if (!addressValidator.supports(Address::class.java)) {
             throw IllegalArgumentException("The supplied [Validator ] must support the validation of [Address] instances.")
         }
    }
    /*
    * This validator validates Customer instances, as well as any Customer subclasses.
    */
    override fun supports(clazz: Class<>): Boolean {
        return Customer::class.java.isAssignableFrom(clazz)
    }
    override fun validate(target: Any, errors: Errors) {
        ValidationUtils.rejectIfEmptyOrWhitespace( errors, "firstName", "field.required")
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "surname", "field.required")
        val customer = target as Customer
        try {
            errors.pushNestedPath("address")
            ValidationUtils.invokeValidator(this. addressValidator, customer.address, errors)
        } finally {
            errors.popNestedPath()
        }
    }
}

Los errores de validación se informan al Errors objeto pasado al validador. En el caso de Spring Web MVC, puede usar la etiqueta <spring:bind/> para verificar los mensajes de error, pero también puede verificar el objeto Errors usted mismo. Puede encontrar más información sobre los métodos que ofrece en javadoc.

Resolución de código en mensajes de error

Hemos cubierto el enlace y la validación de bases de datos. Esta sección analiza la salida de mensajes correspondientes a errores de validación. En el ejemplo que se muestra en la sección anterior, excluimos los campos name y age. Si necesitamos mostrar mensajes de error usando MessageSource, entonces podemos hacerlo usando el código de error que especificamos al rechazar el campo ("nombre" y "edad" en este caso). Si llama (directa o indirectamente, por ejemplo usando la clase ValidationUtils) rejectValue o uno de los otros métodos reject desde Errors interface code, entonces la implementación base no solo registrará el código pasado, sino que también registrará una serie de códigos de error adicionales. El MessageCodesResolver define qué códigos de error registra la interfaz Errors. El valor predeterminado es DefaultMessageCodesResolver, que (por ejemplo) no solo registra un mensaje con el código que usted especifica, sino que también registra mensajes que incluyen el nombre del campo que pasó al método de rechazo. Entonces, si un campo se excluye usando rejectValue("age", "too.darn.old"), además del código too.darn.old, Spring también registra too.darn.old.age y too.darn.old.age.int (el primero incluye el nombre del campo y el segundo el tipo de campo). Esto es para comodidad de los desarrolladores cuando trabajan con mensajes de error.

Puede encontrar más información sobre MessageCodesResolver y la estrategia predeterminada en el javadoc en MessageCodesResolver y DefaultMessageCodesResolver , respectivamente.