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:
public class Person {
private String name;
private int age;
// regular getters and setters...
}
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 esteValidator
¿Puede validar instancias de laClase
proporcionada?validate(Object, org.springframework.validation.Errors)
: Valida el objeto dado y, si detecta errores, los registra en el objeto Errorsdado.
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
:
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");
}
}
}
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:
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();
}
}
}
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.