Creación de validadores con anotaciones

Este va a ser un ejemplo rápido sobre cómo crear validaciones con anotaciones a medida del estilo:

@NotEmpty
private String name;

No pretende ser una explicación rigurosa de todas las opciones (para eso existen unos manuales y unas referencias técnicas fabulosas.

Usamos la validación de beans Bean Validation JSR 303, en su implementación de Hibernate (Hibernate validator 4.1.0.Final)

Si, por ejemplo, al dar de alta un usuario, queremos comprobar que el usuario es único, y no queremos meternos en engorrosas comprobaciones e if’s anidados para ver si un usuario está o no dado de alta, podemos querer crear la anotación siguiente dentro del bean que nos interese:

@UniqueUser
private String username;

Y si no pasa la validación, podremos dar un error de validación sin necesidad de ensuciar el código.

Para ello primero se crea la anotación propiamente dicha, por ejemplo «UniqueUser.java».

@Target({ FIELD, ANNOTATION_TYPE })
@Retention(RUNTIME)
@Constraint(validatedBy = UniqueUserConstraintValidator.class)
@Documented
public @interface UniqueUser {
	String message() default "{user.exists}";
	Class<?>[] groups() default {};
	Class<? extends Payload>[] payload() default {};
}

Lo interesante de aquí es:

  • El nombre que sigue a @interface UniqueUser, que será como llamaremos a la anotación, es decir, utilizaremos @UniqueUser cuando queramos usarla.
  • La línea @Target({ FIELD, ANNOTATION_TYPE }) dice que se podrán anotar campos, pero por ejemplo no métodos (que sería tan sencillo como añadir METHOD)
  • @Retention(RUNTIME) indica que la anotación se aplicará en tiempo de ejecución por reflection. Se escapa de este mini-manual explicarlo en profundidad.
  • @Constraint(validatedBy = UniqueUserConstraintValidator.class) nos indica que la anotación se validará con la clase UniqueUserConstraintValidator, que veremos a continuación.
  • El message es el texto del error. Puede estar internacionalizado en messages_i18n.properties, donde i18n es el locale pertinente.
  • Groups y payload se dejan como arrays vacíos.

La clase UniqueUserConstraintValidator tiene que implementar un ConstraintValidator, y quedaría de la siguiente manera:

public class UniqueUserConstraintValidator implements ConstraintValidator<UniqueUser, String> {

	@Inject
	private UserService userService;

	public void initialize(UniqueUser constraintAnnotation) {
		// No hacemos nada
	}

	public boolean isValid(String username,	ConstraintValidatorContext constraintContext) {

		if (userService.existsUserByUsername(username)) {
			return false;
		} else {
			return true;
		}
	}
}

Como puede verse, hemos inyectado (gracias a Spring) el servicio de usuarios que será el que compruebe si ya existe ese nombre de usuario en el sistema. En el método isValid es donde se valida realmente si el campo username cumple o no la validación, devolviendo true si pasa la validación y false en caso contrario.

Y de esta manera ya tendremos nuestra brand-new anotación @UniqueUser lista para comprobar si el usuario es único.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *