Аннотация для проверки равенства двух полей формы в Spring MVC

июня
21
2012

Содержание

Как уже вспоминалось в предыдущей статье, в этой пойдет речь о создании собственной аннотации для проверки кореектности введенных данных. В качестве примера будет рассмотрено сравнение двух паролей на форме регистрации.

В этой статье будем рассматривать всё тот же пример из двух предыдущих частей статьи о формах в Spring MVC.

Итак, класс SignupForm теперь будет выглядеть следующим образом:


package com.seostella.springcustomconstraints.form;

import javax.validation.constraints.Size;

import org.hibernate.validator.constraints.Email;
import org.hibernate.validator.constraints.NotBlank;

import com.seostella.constraints.FieldEquals;

@FieldEquals( field="password", equalsTo="confirmPassword" )
public class SignupForm {
	@NotBlank
	@Size(min=3, max=16)
	private String username;
	
	@NotBlank
	private String password;
	
	@NotBlank
	private String confirmPassword;
	
	@Email
	private String email;
	
	public String getUsername() {
		return username;
	}
	
	public void setUsername(String username) {
		this.username = username;
	}
	
	public String getPassword() {
		return password;
	}
	
	public void setPassword(String password) {
		this.password = password;
	}
	
	public String getConfirmPassword() {
		return confirmPassword;
	}
	
	public void setConfirmPassword(String confirmPassword) {
		this.confirmPassword = confirmPassword;
	}
	
	public String getEmail() {
		return email;
	}
	
	public void setEmail(String email) {
		this.email = email;
	}
}

Как видно из листинга, добавилась одна аннотация к классу:


@FieldEquals( field="password", equalsTo="confirmPassword" )

Нам необходимо создать новую аннотацию @FieldEquals для обработки валидности полей формы. Код аннотации ниже:


package com.seostella.constraints;

import javax.validation.Constraint;
import javax.validation.Payload;

import com.seostella.constraints.impl.FieldEqualsValidator;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

@Target(TYPE)
@Retention(RUNTIME)
@Constraint(validatedBy = FieldEqualsValidator.class)
@Documented
public @interface FieldEquals {
	public static final String MESSAGE = "fields.notMatches";

	String message() default MESSAGE;

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

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

	@Target(TYPE)
	@Retention(RUNTIME)
	@Documented
	@interface List {
		FieldEquals[] value();
	}

	String field();

	String equalsTo();
}

Об аннотациях уже было подробно рассказано раннее (см. статью "Аннотации в Java. Введение" и ее продолжения), поэтому в этой статье остановимся только на основных моментах:

@Target(TYPE) - данная аннотация может описывать только класс, интерфейс, enum или другую аннотацию.

@Retention(RUNTIME) - аннотация доступна во время выполнения приложения.

@Constraint(validatedBy = FieldEqualsValidator.class) - этой аннотацией указывается, какой класс будет проверять на правильность данные. Это не стандартная аннотация, но она необходимо чтобы сработал автоматический механизм проверки данных.

@Documented - указываем, что сгенерированный JavaDoc будет содержать данную аннотацию.

Почти всё тело аннотации FieldEquals является шаблоном, который необходим по умолчанию для каждой аннотации (в виду того, что для аннотаций не предусмотрен механизм наследования):


String message() default MESSAGE;

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

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

@Target(TYPE)
@Retention(RUNTIME)
@Documented
@interface List {
	FieldEquals[] value();
}

Два поля, которые являются уникальными для аннотации FieldEquals: field и equalsTo. Это поля, которые будут сравниваться.

Класс FieldEqualsValidator, который проверяет правильность данных:


package com.seostella.constraints.impl;

import java.lang.reflect.Method;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

import com.seostella.constraints.FieldEquals;

public class FieldEqualsValidator implements
		ConstraintValidator<FieldEquals, Object> {
	private String field;
	private String equalsTo;
	private String message = FieldEquals.MESSAGE;

	public void initialize(FieldEquals constraintAnnotation) {
		this.message = constraintAnnotation.message();
		this.field = constraintAnnotation.field();
		this.equalsTo = constraintAnnotation.equalsTo();
	}

	public boolean isValid(Object value, ConstraintValidatorContext context) {
		try {
			final Object fieldObject = getProperty(value, field, null);
			final Object equalsToObject = getProperty(value, equalsTo, null);

			if (fieldObject == null && equalsToObject == null) {
				return true;
			}

			boolean matches = (fieldObject != null)
					&& fieldObject.equals(equalsToObject);

			if (!matches) {
				String msg = this.message;
				if( this.message == null  
						|| "".equals( this.message ) 
						|| FieldEquals.MESSAGE.equals( this.message ) ){
					msg = field + " is not equal to " + equalsTo;
				}
				context.disableDefaultConstraintViolation();
				context.buildConstraintViolationWithTemplate( msg )
						.addNode(equalsTo).addConstraintViolation();
			}

			return matches;
		} catch (final Exception e) {
			e.printStackTrace();
		}
		return true;
	}

	private Object getProperty(Object value, String fieldName,
			Object defaultValue) {
		Class<?> clazz = value.getClass();
		String methodName = "get" + Character.toUpperCase(fieldName.charAt(0))
				+ fieldName.substring(1);
		try {
			Method method = clazz.getDeclaredMethod(methodName, new Class[0]);
			return method.invoke(value);
		} catch (Exception e) {
		}
		return defaultValue;
	}
}

В этом классе всё немного проще. Он должен реализовывать интерфейс ConstraintValidator и его два метода: initialize() и isValid().

В метод initialize() параметром передается аннотация, в которой содержится необходимая информация: сообщение об ошибке и два поля:


public void initialize(FieldEquals constraintAnnotation) {
	this.message = constraintAnnotation.message();
	this.field = constraintAnnotation.field();
	this.equalsTo = constraintAnnotation.equalsTo();
}

В методе isValid() происходит обработка данных. Если метод возвращает true, данные считаются правильными, если false - данные не верны и пользователю будет отображена ошибка:


public boolean isValid(Object value, ConstraintValidatorContext context) {
	try {
		final Object fieldObject = getProperty(value, field, null);
		final Object equalsToObject = getProperty(value, equalsTo, null);

		if (fieldObject == null && equalsToObject == null) {
			return true;
		}

		boolean matches = (fieldObject != null)
				&& fieldObject.equals(equalsToObject);

		if (!matches) {
			String msg = this.message;
			if( this.message == null  
					|| "".equals( this.message ) 
					|| FieldEquals.MESSAGE.equals( this.message ) ){
				msg = field + " is not equal to " + equalsTo;
			}
			context.disableDefaultConstraintViolation();
			context.buildConstraintViolationWithTemplate( msg )
					.addNode(equalsTo).addConstraintViolation();
		}

		return matches;
	} catch (final Exception e) {
		e.printStackTrace();
	}
	return true;
}

Параметр value метода isValid() является значением, которое необходимо проверить на правильность данных. В данном случае, это объект класса SignForm. С помощью рефлексии в следующем коде получаем значения двух свойств, которые необходимо сравнить:


final Object fieldObject = getProperty(value, field, null);
final Object equalsToObject = getProperty(value, equalsTo, null);

Далее происходит обычное сравнивание двух объектов и еще одна интересная вещь. Следующим кодом возвращается ошибка, собранная из двух полей если пользователь не переопределил поле message:


String msg = this.message;
if( this.message == null  
		|| "".equals( this.message ) 
		|| FieldEquals.MESSAGE.equals( this.message ) ){
	msg = field + " is not equal to " + equalsTo;
}
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate( msg )
		.addNode(equalsTo).addConstraintViolation();

Эта ошибка будет выглядеть так:


password is not equal to confirmPassword

Код, который демонстрирует работу аннотации FieldEquals, Вы можете скачать по следующей ссылке - Скачать spring-custom-constraints.zip.

< Проверка данных формы с помощью аннотаций (@Size, @Email и др) в Spring MVC

Комментарии (1)

val111
12 сентября 2012 г. 9:33
Спасибо за подборку статей.
Толково подобрано по тематикам. Ничего лишнего(типа: посмотрите какой я умный). И достаточно свежие темы.
Редакторам респект. Просьба сохранять стиль и желание писать.
Вы должны войти под своим аккаунтом чтобы оставлять комментарии