Spring MVC: как выполнить проверку?

Я хотел бы знать, каков самый чистый и лучший способ выполнить проверку формы вводимых пользователем данных. Я видел, как некоторые разработчики реализовали org.springframework.validation.Validator. Вопрос по этому поводу: я видел, как он проверяет класс. Должен ли класс заполняться вручную значениями из пользовательского ввода, а затем передаваться валидатору?

Я не понимаю, как лучше всего проверять вводимые пользователем данные. Я знаю о традиционном методе использования request.getParameter() с последующей проверкой nulls вручную, но я не хочу выполнять всю проверку в моем Controller. Будем очень признательны за несколько хороших советов в этой области. Я не использую Hibernate в этом приложении.


person devdar    schedule 27.08.2012    source источник
comment
spring.io/guides/gs/validating-form-input   -  person OrangeDog    schedule 07.11.2016


Ответы (6)


В Spring MVC есть 3 различных способа выполнения проверки: с помощью аннотации, вручную или их сочетание. Не существует единственного «самого чистого и лучшего способа» проверки, но, вероятно, есть тот, который лучше подходит для вашего проекта / проблемы / контекста.

У нас есть Пользователь:

public class User {

    private String name;

    ...

}

Метод 1. Если у вас есть Spring 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: если у пользователя окажется пустое имя, result.hasErrors () будет истинным.

Метод 2. Если у вас сложная проверка (например, логика проверки для крупного бизнеса, условная проверка по нескольким полям и т. д.) или по какой-то причине вы не можете использовать метод 1, используйте ручную проверку. Рекомендуется отделить код контроллера от логики проверки. Не создавайте свои классы проверки с нуля, Spring предоставляет удобный интерфейс org.springframework.validation.Validator (начиная со Spring 2).

Итак, скажем, у вас есть

public class User {

    private String name;

    private Integer birthYear;
    private User responsibleUser;
    ...

}

и вы хотите выполнить некоторую "сложную" проверку, например: если возраст пользователя меньше 18 лет, ответственныйUser не должен быть нулевым, а возраст ответственного пользователя должен быть старше 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
        }
}

Если есть ошибки проверки, result.hasErrors () будет истинным.

Примечание. Вы также можете установить валидатор в методе @InitBinder контроллера с помощью "binder.setValidator (...)" (в этом случае смешанное использование методов 1 и 2 будет невозможно, потому что вы замените значение по умолчанию валидатор). Или вы можете создать его экземпляр в конструкторе по умолчанию контроллера. Или используйте @ Component / @ Service UserValidator, который вы вводите (@Autowired) в свой контроллер: очень полезно, потому что большинство валидаторов являются одиночными + имитация модульных тестов становится проще + ваш валидатор может вызывать другие компоненты Spring.

Метод 3. Почему бы не использовать комбинацию обоих методов? Подтвердите простые вещи, такие как атрибут «name», с помощью аннотаций (это быстро, кратко и более читабельно). Сохраняйте тяжелые проверки для валидаторов (когда кодирование настраиваемых сложных аннотаций проверки может занять несколько часов или просто когда невозможно использовать аннотации). Я сделал это в предыдущем проекте, это сработало отлично, быстро и легко.

Предупреждение: не следует путать обработку проверки с обработкой исключений. Прочтите этот пост, чтобы узнать, когда их использовать.

Использованная литература :

person Community    schedule 27.08.2012
comment
Можете ли вы сказать мне, что должен иметь мой servlet.xml для этой конфигурации. Я хочу вернуть ошибки в представление - person devdar; 01.09.2012
comment
@dev_darin Вы имеете в виду конфиг для проверки JSR-303? - person Jerome Dalbert; 01.09.2012
comment
да, проверка JSR-303, я повторно принял это, извините, теперь я понял, что вы можете принять только один ответ. steve.hanson ответ тоже был отличным, просто пытался быть справедливым по отношению к двум знающим людям - person devdar; 01.09.2012
comment
@dev_marin Для проверки в Spring 3.x + нет ничего особенного в servlet.xml или [servlet-name] -servlet.xml. Вам просто нужен jar-файл hibernate-validator в ваших библиотеках проекта (или через Maven). Вот и все, тогда должно работать. Предупреждение, если вы используете метод 3: по умолчанию каждый контроллер имеет доступ к валидатору JSR-303, поэтому будьте осторожны, чтобы не переопределить его с помощью setValidator. Если вы хотите добавить настраиваемый валидатор поверх, просто создайте его экземпляр и используйте или введите его (если это компонент Spring). Если у вас все еще есть проблемы после проверки документов Google и Spring, вам следует опубликовать новый вопрос. - person Jerome Dalbert; 02.09.2012
comment
Для смешанного использования методов 1 и 2 есть способ использовать @InitBinder. Вместо binder.setValidator (...) можно использовать binder.addValidators (...) - person jasonfungsing; 05.11.2014
comment
Поправьте меня, если я ошибаюсь, но вы можете смешивать проверку с помощью аннотаций JSR-303 (метод 1) и настраиваемую проверку (метод 2) при использовании аннотации @InitBinder. Просто используйте binder.addValidators (userValidator) вместо binder.setValidator (userValidator), и оба метода проверки вступят в силу. - person SebastianRiemer; 20.08.2015
comment
Доступна ли проверка компонентов только для методов контроллера (помеченных @RequestMapping)? Я использую это простым методом, это не работает. - person DiveInto; 15.06.2016
comment
Спасибо за пояснение, что нет «чистого и лучшего» способа создавать сложные бизнес-правила, это экономит мне время на поиск в Google библии проверки бизнеса, лол - person ChengWhyNot; 19.09.2018

Есть два способа проверить вводимые пользователем данные: аннотации и наследование класса Spring Validator. Для простых случаев хороши аннотации. Если вам нужны сложные проверки (например, перекрестная проверка, например, поле «проверить адрес электронной почты»), или если ваша модель проверена в нескольких местах вашего приложения с разными правилами, или если у вас нет возможности изменить свой объект модели, разместив на нем аннотации, Spring основанный на наследовании 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 аннотации. Вы также можете использовать org.hibernate.validator.constraints Hibernate, но это не похоже на то, что вы используете Hibernate.

В качестве альтернативы, если вы реализуете Spring Validator, вы должны создать класс следующим образом:

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());
}

См. Также Документы Spring.

Надеюсь, это поможет.

person stephen.hanson    schedule 27.08.2012
comment
при использовании Spring Validator мне нужно установить pojo с контроллера, а затем проверить его? - person devdar; 27.08.2012
comment
Я не уверен, что понимаю ваш вопрос. Если вы видите фрагмент кода контроллера, Spring автоматически привязывает отправленную форму к параметру Foo в методе обработчика. Вы можете уточнить? - person stephen.hanson; 27.08.2012
comment
хорошо, я говорю, когда пользователь отправляет пользовательские вводы, Контроллер получает HTTP-запрос, оттуда происходит то, что вы используете request.getParameter () для получения всех параметров пользователя, затем устанавливаете значения в POJO, а затем передаете класс к объекту проверки. Класс проверки отправит ошибки обратно в представление с ошибками, если таковые были обнаружены. Так ли это бывает? - person devdar; 28.08.2012
comment
Это могло бы произойти так, но есть более простой способ ... Если вы используете JSP и ‹form: form commandName = user› submission, данные автоматически помещаются в @ModelAttribute (пользователь) пользователя в методе контроллера. См. Документ: static.springsource. org / spring / docs / 3.0.x / - person Jerome Dalbert; 28.08.2012
comment
+1, потому что это первый найденный мной пример, в котором используется @ModelAttribute; без него ни один из найденных мною туториалов не работает. - person Riccardo Cossu; 22.04.2014
comment
Почему, но зачем делать new CustomValidator()? Лучше всего сослаться на наш bean-компонент и внедрить его с помощью spring context @Autowired CustomValidator customValidator;, чтобы таким образом вы могли получить лучшее от контейнера IoC. - person BendaThierry.com; 29.08.2016
comment
Ссылка на статью - DOA: Ошибка при установлении соединения с базой данных - person Madbreaks; 27.02.2018
comment
@Madbreaks, спасибо. Я недавно отключил свой старый сайт. Удалил ссылку. - person stephen.hanson; 28.02.2018

Я хотел бы продолжить приятный ответ Джерома Далберта. Мне очень легко было написать свои собственные валидаторы аннотаций на языке JSR-303. Вы не ограничены проверкой «одним полем». Вы можете создать свою собственную аннотацию на уровне типа и выполнить сложную проверку (см. Примеры ниже). Я предпочитаю этот способ, потому что мне не нужно смешивать разные типы проверки (Spring и JSR-303), как это делает Джером. Кроме того, эти валидаторы "осведомлены о Spring", поэтому вы можете использовать @ 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;

}
person michal.kreuzman    schedule 10.05.2013
comment
Мне также было интересно, что контроллер обычно имеет один валидатор, и я видел, где у вас может быть несколько валидаторов, однако, если у вас есть набор проверок, определенных для одного объекта, однако операция, которую вы хотите выполнить для объекта, отличается, например. при сохранении / обновлении для сохранения требуется определенный набор проверок, а при обновлении требуется другой набор проверок. Есть ли способ настроить класс валидатора для проведения всей валидации на основе операции или вам потребуется использовать несколько валидаторов? - person devdar; 10.05.2013
comment
У вас также может быть проверка аннотации для метода. Так что вы можете создать свою собственную проверку домена, если я понимаю ваш вопрос. Для этого необходимо указать ElementType.METHOD в @Target. - person michal.kreuzman; 10.05.2013
comment
я понимаю, о чем вы говорите, можете вы также указать мне пример для более четкой картины. - person devdar; 12.05.2013

Если у вас одинаковая логика обработки ошибок для разных обработчиков методов, вы получите множество обработчиков со следующим шаблоном кода:

if (validation.hasErrors()) {
  // do error handling
}
else {
  // do the actual business logic
}

Предположим, вы создаете службы RESTful и хотите возвращать 400 Bad Request вместе с сообщениями об ошибках для каждого случая ошибки проверки. Тогда часть обработки ошибок будет одинаковой для каждой конечной точки REST, требующей проверки. Повторять ту же самую логику в каждом отдельном обработчике не так СУХОЙ херня!

Один из способов решить эту проблему - удалить сразу BindingResult после каждого bean-объекта To-Be-Validated. Теперь ваш обработчик будет таким:

@RequestMapping(...)
public Something doStuff(@Valid Somebean bean) { 
    // do the actual business logic
    // Just the else part!
}

Таким образом, если связанный bean-компонент недействителен, Spring будет сгенерирован MethodArgumentNotValidException. Вы можете определить ControllerAdvice, который обрабатывает это исключение, используя ту же логику обработки ошибок:

@ControllerAdvice
public class ErrorHandlingControllerAdvice {
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public SomeErrorBean handleValidationError(MethodArgumentNotValidException ex) {
        // do error handling
        // Just the if part!
    }
}

Вы все еще можете исследовать лежащий в основе BindingResult, используя getBindingResult метод MethodArgumentNotValidException.

person Ali Dehghani    schedule 21.04.2016

Найдите полный пример проверки 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);
    }
}
person Vicky    schedule 13.11.2013

Поместите этот компонент в свой класс конфигурации.

 @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();
}

для проверки bean-компонента вручную. Затем вы получите весь результат в BindingResult, и вы можете получить оттуда.

person praveen jain    schedule 15.02.2018