Spring MVC:Как выполнить проверку?
Я хотел бы знать, какой самый чистый и лучший способ выполнить проверку формы пользовательских входов. Я видел, как некоторые разработчики реализуют org.springframework.validation.Validator
. Вопрос об этом: я видел, что он проверяет класс. Должен ли класс заполняться вручную значениями из пользовательского ввода, а затем передаваться валидатору?
Я смущен самым чистым и лучшим способом проверки пользовательского ввода. Я знаю о традиционном методе использования request.getParameter()
и затем проверка вручную для nulls
, но я не хочу делать ВСЮ проверку в моем Controller
. Некоторые хорошие советы в этой области будут очень признательны. Я не использую Hibernate в этом приложении.
6 ответов
С Spring MVC существует 3 разных способа выполнения проверки : использование аннотации, вручную или сочетание обоих. Существует не уникальный "самый чистый и лучший способ" для проверки, но, вероятно, есть тот, который лучше соответствует вашему проекту/проблеме/контексту.
давайте иметь пользователя:
public class User {
private String name;
...
}
Способ 1 : если у вас есть пружина 3.X+ и простая проверка чтобы сделать, используйте javax.validation.constraints
аннотации (также известные как аннотации JSR-303).
public class User {
@NotNull
private String name;
...
}
вы потребуется поставщик JSR-303 в ваших библиотеках, например Hibernate Validator кто является эталонной реализацией (эта библиотека не имеет ничего общего с базами данных и реляционного отображения, он просто делает проверку :-).
тогда в вашем контроллере у вас будет что-то вроде :
@RequestMapping(value="/user", method=RequestMethod.POST)
public createUser(Model model, @Valid @ModelAttribute("user") User user, BindingResult result){
if (result.hasErrors()){
// do something
}
else {
// do something else
}
}
обратите внимание на @Valid: если у пользователя есть нулевое имя, результат.hasErrors() будет истина.
Способ 2 : если у вас сложная проверка (например, логика проверки большого бизнеса, условная проверка в нескольких полях и т. д.), или по какой-то причине вы не можете использовать метод 1, используйте ручную проверку. Рекомендуется отделить код контроллера от логики проверки. Не создавайте свой класс(классы) проверки с нуля, Spring предоставляет удобный org.springframework.validation.Validator
интерфейс (с весны 2).
Итак, предположим, у вас есть
public class User {
private String name;
private Integer birthYear;
private User responsibleUser;
...
}
и вы хотите сделать некоторую "сложную" проверку, например : если возраст пользователя до 18 лет responsibleUser не должен быть нулевым, а возраст responsibleUser должен быть старше 21 года.
вы сделаете что-то вроде этого
public class UserValidator implements Validator {
@Override
public boolean supports(Class clazz) {
return User.class.equals(clazz);
}
@Override
public void validate(Object target, Errors errors) {
User user = (User) target;
if(user.getName() == null) {
errors.rejectValue("name", "your_error_code");
}
// do "complex" validation here
}
}
тогда в вашем контроллере у вас будет:
@RequestMapping(value="/user", method=RequestMethod.POST)
public createUser(Model model, @ModelAttribute("user") User user, BindingResult result){
UserValidator userValidator = new UserValidator();
userValidator.validate(user, result);
if (result.hasErrors()){
// do something
}
else {
// do something else
}
}
если есть ошибки проверки, результат.hasErrors() будет истина.
Примечание: Вы также можете установить валидатор в методе @InitBinder контроллера с помощью " binder.setValidator(...) "(в этом случае смешанное использование методов 1 и 2 будет невозможно, поскольку заменить валидатор). Или вы можете создать его экземпляр в конструкторе контроллера по умолчанию. Или у вас есть@Component/ @ Service UserValidator, который вы вводите (@Autowired) в свой контроллер : очень полезно, потому что большинство валидаторов-синглеты + модульный тест становится проще + ваш валидатор может вызывать другие компоненты Spring.
Способ 3 : Почему бы не использовать сочетание обоих методов? Проверьте простые вещи, такие как атрибут" name", с помощью аннотации (это быстро сделать, кратким и более читаемым). Сохраняйте тяжелые проверки для валидаторов (когда потребуется несколько часов для кодирования пользовательских сложных аннотаций проверки или когда невозможно использовать аннотации). Я сделал это на прежнем проекте, он работал как шарм, быстро и легко.
предупреждение : вы не должны ошибиться проверка обращения на обработка исключений. читать этот пост чтобы знать, когда использовать их.
ссылки :
существует два способа проверки ввода пользователя: аннотации и наследование класса валидатора Spring. Для простых случаев аннотации хороши. Если вам нужны сложные проверки (например, проверка кросс-поля, например. поле" проверить адрес электронной почты"), или если ваша модель проверена в нескольких местах в вашем приложении с разными правилами, или если у вас нет возможности изменить объект модели, разместив на нем аннотации, Spring's inheritance-based Validator-это путь. Я покажу примеры обоих.
фактическая часть проверки одинакова независимо от того, какой тип проверки вы используете:
RequestMapping(value="fooPage", method = RequestMethod.POST)
public String processSubmit(@Valid @ModelAttribute("foo") Foo foo, BindingResult result, ModelMap m) {
if(result.hasErrors()) {
return "fooPage";
}
...
return "successPage";
}
если вы используете аннотации, ваша Foo
класс может выглядеть так:
public class Foo {
@NotNull
@Size(min = 1, max = 20)
private String name;
@NotNull
@Min(1)
@Max(110)
private Integer age;
// getters, setters
}
аннотации выше javax.validation.constraints
Примечание. Вы также можете использовать Hibernate
org.hibernate.validator.constraints
, но не похоже, что вы используете Hibernate.
кроме того, если вы реализуете валидатор Spring, вы создадите класс как следует:
public class FooValidator implements Validator {
@Override
public boolean supports(Class<?> clazz) {
return Foo.class.equals(clazz);
}
@Override
public void validate(Object target, Errors errors) {
Foo foo = (Foo) target;
if(foo.getName() == null) {
errors.rejectValue("name", "name[emptyMessage]");
}
else if(foo.getName().length() < 1 || foo.getName().length() > 20){
errors.rejectValue("name", "name[invalidLength]");
}
if(foo.getAge() == null) {
errors.rejectValue("age", "age[emptyMessage]");
}
else if(foo.getAge() < 1 || foo.getAge() > 110){
errors.rejectValue("age", "age[invalidAge]");
}
}
}
при использовании вышеуказанного валидатора вам также нужно привязать валидатор к контроллеру Spring (не обязательно при использовании аннотаций):
@InitBinder("foo")
protected void initBinder(WebDataBinder binder) {
binder.setValidator(new FooValidator());
}
см. Также весной документы.
надеюсь, что это поможет.
Я хотел бы выразить хороший ответ Джерома Dalbert. Я нашел очень легко написать свои собственные валидаторы аннотаций в JSR-303 способом. Вы не ограничены проверкой "одного поля". Вы можете создать свою собственную аннотацию на уровне типа и иметь сложную проверку (см. примеры ниже). Я предпочитаю этот способ, потому что мне не нужно смешивать разные типы проверки (Spring и JSR-303), как это делает Джером. Также эти валидаторы "знают о весне", поэтому вы можете использовать @Inject/@Autowire из коробка.
пример проверки пользовательского объекта:
@Target({ TYPE, ANNOTATION_TYPE })
@Retention(RUNTIME)
@Constraint(validatedBy = { YourCustomObjectValidator.class })
public @interface YourCustomObjectValid {
String message() default "{YourCustomObjectValid.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
public class YourCustomObjectValidator implements ConstraintValidator<YourCustomObjectValid, YourCustomObject> {
@Override
public void initialize(YourCustomObjectValid constraintAnnotation) { }
@Override
public boolean isValid(YourCustomObject value, ConstraintValidatorContext context) {
// Validate your complex logic
// Mark field with error
ConstraintViolationBuilder cvb = context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate());
cvb.addNode(someField).addConstraintViolation();
return true;
}
}
@YourCustomObjectValid
public YourCustomObject {
}
пример равенства общих полей:
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
@Target({ TYPE, ANNOTATION_TYPE })
@Retention(RUNTIME)
@Constraint(validatedBy = { FieldsEqualityValidator.class })
public @interface FieldsEquality {
String message() default "{FieldsEquality.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
/**
* Name of the first field that will be compared.
*
* @return name
*/
String firstFieldName();
/**
* Name of the second field that will be compared.
*
* @return name
*/
String secondFieldName();
@Target({ TYPE, ANNOTATION_TYPE })
@Retention(RUNTIME)
public @interface List {
FieldsEquality[] value();
}
}
import java.lang.reflect.Field;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.ReflectionUtils;
public class FieldsEqualityValidator implements ConstraintValidator<FieldsEquality, Object> {
private static final Logger log = LoggerFactory.getLogger(FieldsEqualityValidator.class);
private String firstFieldName;
private String secondFieldName;
@Override
public void initialize(FieldsEquality constraintAnnotation) {
firstFieldName = constraintAnnotation.firstFieldName();
secondFieldName = constraintAnnotation.secondFieldName();
}
@Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
if (value == null)
return true;
try {
Class<?> clazz = value.getClass();
Field firstField = ReflectionUtils.findField(clazz, firstFieldName);
firstField.setAccessible(true);
Object first = firstField.get(value);
Field secondField = ReflectionUtils.findField(clazz, secondFieldName);
secondField.setAccessible(true);
Object second = secondField.get(value);
if (first != null && second != null && !first.equals(second)) {
ConstraintViolationBuilder cvb = context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate());
cvb.addNode(firstFieldName).addConstraintViolation();
ConstraintViolationBuilder cvb = context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate());
cvb.addNode(someField).addConstraintViolation(secondFieldName);
return false;
}
} catch (Exception e) {
log.error("Cannot validate fileds equality in '" + value + "'!", e);
return false;
}
return true;
}
}
@FieldsEquality(firstFieldName = "password", secondFieldName = "confirmPassword")
public class NewUserForm {
private String password;
private String confirmPassword;
}
если у вас есть такая же логика обработки ошибок для разных обработчиков методов, то вы получите множество обработчиков со следующим шаблоном кода:
if (validation.hasErrors()) {
// do error handling
}
else {
// do the actual business logic
}
Предположим, вы создаете RESTful services и хотите вернуться 400 Bad Request
вместе с сообщениями об ошибках для каждого случая ошибки проверки. Затем часть обработки ошибок будет одинаковой для каждой конечной точки REST, требующей проверки. Повторение этой самой логики в каждом отдельном обработчике не так сухойишь!
один из способов решить эту проблему-отказаться от немедленного BindingResult
после каждого Для Проверки бобовые. Теперь, ваш обработчик будет выглядеть так:
@RequestMapping(...)
public Something doStuff(@Valid Somebean bean) {
// do the actual business logic
// Just the else part!
}
таким образом, если связанный Боб недействителен, a MethodArgumentNotValidException
будет брошен весной. Вы можете определить ControllerAdvice
который обрабатывает это исключение с той же логикой обработки ошибок:
@ControllerAdvice
public class ErrorHandlingControllerAdvice {
@ExceptionHandler(MethodArgumentNotValidException.class)
public SomeErrorBean handleValidationError(MethodArgumentNotValidException ex) {
// do error handling
// Just the if part!
}
}
вы все еще можете изучить основной BindingResult
используя getBindingResult
метод MethodArgumentNotValidException
.
найти полный пример проверки Spring Mvc
import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;
import com.technicalkeeda.bean.Login;
public class LoginValidator implements Validator {
public boolean supports(Class aClass) {
return Login.class.equals(aClass);
}
public void validate(Object obj, Errors errors) {
Login login = (Login) obj;
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "userName",
"username.required", "Required field");
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "userPassword",
"userpassword.required", "Required field");
}
}
public class LoginController extends SimpleFormController {
private LoginService loginService;
public LoginController() {
setCommandClass(Login.class);
setCommandName("login");
}
public void setLoginService(LoginService loginService) {
this.loginService = loginService;
}
@Override
protected ModelAndView onSubmit(Object command) throws Exception {
Login login = (Login) command;
loginService.add(login);
return new ModelAndView("loginsucess", "login", login);
}
}
поместите этот компонент в свой класс конфигурации.
@Bean
public Validator localValidatorFactoryBean() {
return new LocalValidatorFactoryBean();
}
и тогда вы можете использовать
<T> BindingResult validate(T t) {
DataBinder binder = new DataBinder(t);
binder.setValidator(validator);
binder.validate();
return binder.getBindingResult();
}
для проверки компонента вручную. Затем вы получите весь результат в BindingResult, и вы можете получить оттуда.