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


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

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

6 137

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, используйте ручную проверку. Рекомендуется отделить код контроллера от логики проверки. Не создавайте свой класс(ы) проверки с нуля, Весна обеспечивает удобный org.springframework.validation.Validator интерфейс (с весны 2).

Итак, допустим, у вас есть

public class User {

    private String name;

    private Integer birthYear;
    private User responsibleUser;
    ...

}

и вы хотите сделать некоторую" сложную " проверку, например : если возраст пользователя до 18 лет ответственный пользователь не должен быть нулевым, а возраст ответственного пользователя должен быть старше 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 : Почему бы не использовать комбинацию обоих методов? Проверьте простые вещи, такие как атрибут" имя", с помощью аннотации (это быстро сделать, кратким и более читаемым). Сохраняйте тяжелые проверки для валидаторов (когда потребуется несколько часов для кодирования пользовательских сложных аннотаций проверки или просто когда невозможно использовать аннотации). Я сделал это на прежнем проекте, он работал как шарм, быстро и легко.

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

ссылки :

существует два способа проверки пользовательского ввода: аннотации и наследование класса валидатора Spring. Для простых случаев аннотации хороши. Если вам нужны сложные проверки (например, кросс-полевая проверка, например. поле" проверить адрес электронной почты"), или если ваша модель проверена в нескольких местах в вашем приложении с разными правилами, или если у вас нет возможности изменить объект модели, поместив на него аннотации, то валидатор Spring на основе наследования-это путь. Я покажу примеры того и другого.

фактическая часть проверки одинакова независимо от того, какой тип проверки вы используете:

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

см. Также Spring docs.

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

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