Анотація для перевірки рівності двох полів форми в 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 р. 09:33
Спасибо за подборку статей.
Толково подобрано по тематикам. Ничего лишнего(типа: посмотрите какой я умный). И достаточно свежие темы.
Редакторам респект. Просьба сохранять стиль и желание писать.
Ви повинні увійти під своїм аккаунтом щоб залишати коментарі