Spring MVC: jak przeprowadzić walidację?

Chciałbym wiedzieć, jaki jest najczystszy i najlepszy sposób na weryfikację danych wejściowych użytkownika. Widziałem, jak niektórzy programiści wdrażają org.springframework.validation.Validator. Pytanie o to: widziałem, że sprawdza klasę. Czy klasa musi być wypełniona ręcznie wartościami z danych wejściowych użytkownika, a następnie przekazana do walidatora?

Jestem zdezorientowany co do najczystszego i najlepszego sposobu weryfikacji danych wejściowych użytkownika. Wiem o tradycyjnej metodzie używania request.getParameter(), a następnie ręcznego sprawdzania pod kątem nulls, ale nie chcę robić wszystkich walidacji w moim Controller. Niektóre dobre rady w tej dziedzinie będą bardzo mile widziane. Nie używam Hibernate w tej aplikacji.

Author: patstuart, 2012-08-27

6 answers

W Spring MVC istnieją 3 różne sposoby walidacji: używając adnotacji, ręcznie lub mieszając oba. Nie ma unikalnego "najczystszego i najlepszego sposobu" na walidację, ale prawdopodobnie jest taki, który lepiej pasuje do twojego projektu/problemu/kontekstu.

Let ' s have a User:

public class User {

    private String name;

    ...

}

Metoda 1: Jeśli masz Spring 3.x+ i prosta Walidacja do zrobienia, użyj adnotacji javax.validation.constraints (znanych również jako adnotacje JSR-303).

public class User {

    @NotNull
    private String name;

    ...

}

Będziesz potrzebował dostawcy JSR-303 w swoim biblioteki, takie jak walidator Hibernate, który jest implementacją referencyjną (ta biblioteka nie ma nic wspólnego z bazami danych i mapowaniem relacyjnym, po prostu sprawdza walidację :-).

Wtedy w Twoim kontrolerze będziesz miał coś w stylu:

@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
    }
}

Zwróć uwagę na @Valid: jeśli użytkownik ma null name, result.hasErrors() będzie prawdą.

Metoda 2: Jeśli masz walidację złożoną (jak logika walidacji dużych firm, Walidacja warunkowa w poprzek wiele pól itp.), lub z jakiegoś powodu nie możesz użyć metody 1, Użyj walidacji ręcznej. Dobrą praktyką jest oddzielanie kodu kontrolera od logiki walidacji. Nie twórz klas walidacji od zera, Spring zapewnia poręczny interfejs org.springframework.validation.Validator (od Spring 2).

Więc powiedzmy, że masz

public class User {

    private String name;

    private Integer birthYear;
    private User responsibleUser;
    ...

}

I chcesz wykonać" złożoną " walidację, na przykład: jeśli wiek użytkownika jest poniżej 18, responsible User nie może być null, a responsible user musi być powyżej 21.

Zrobisz coś takiego

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

    }

}

Wtedy w Twoim kontrolerze będziesz miał :

@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
        }
}

Jeśli występują błędy walidacji, wynik.hasErrors() będzie prawdą.

Uwaga: walidator można również ustawić w metodzie @initbinder kontrolera za pomocą " binder.setValidator(...) "(w takim przypadku użycie metody mix 1 i 2 nie byłoby możliwe, ponieważ zastępujesz domyślny walidator). Możesz też utworzyć instancję w domyślnym konstruktorze kontrolera. Lub użyj @ Component / @ Service UserValidator, który wstrzykniesz (@Autowired) do kontrolera : bardzo przydatne, ponieważ większość walidatorów to singletony + testowanie jednostkowe staje się łatwiejsze + walidator może wywoływać inne składniki sprężyny.

Metoda 3: Dlaczego nie użyć kombinacji obu metod? Zweryfikuj proste rzeczy, takie jak atrybut "name", za pomocą adnotacji (jest to szybkie, zwięzłe i bardziej czytelne). Zachowaj ciężkie walidacje dla walidatorów (kiedy to zajmie godzin na kodowanie niestandardowych złożonych adnotacji walidacyjnych lub po prostu, gdy nie jest możliwe użycie adnotacji). Zrobiłem to na poprzednim projekcie, działał jak urok, szybko i łatwo.

Uwaga: nie wolno mylić obsługi walidacji z obsługą WYJĄTKÓW . przeczytaj ten post aby wiedzieć, kiedy z nich korzystać.

Bibliografia:

 300
Author: Jerome Dalbert,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:18:20

Są dwa sposoby walidacji danych wejściowych użytkownika: adnotacje i dziedziczenie klasy walidatora Springa. W prostych przypadkach adnotacje są ładne. Jeśli potrzebujesz złożonych walidacji (takich jak Walidacja między polami, np. pole" zweryfikuj adres e-mail") lub jeśli twój model jest sprawdzany w wielu miejscach w aplikacji z różnymi regułami lub jeśli nie masz możliwości modyfikowania obiektu modelu poprzez umieszczanie na nim adnotacji, walidator oparty na dziedziczeniu Springa jest dobrym rozwiązaniem. Pokażę przykłady obu.

Rzeczywista część walidacji jest taka sama niezależnie od tego, jakiego typu walidacji używasz:

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";
}

Jeśli używasz adnotacji, twoja klasa Foo może wyglądać następująco:

public class Foo {

    @NotNull
    @Size(min = 1, max = 20)
    private String name;

    @NotNull
    @Min(1)
    @Max(110)
    private Integer age;

    // getters, setters
}

Adnotacje powyżej to javax.validation.constraints adnotacje. Możesz również użyć Hibernate ' s org.hibernate.validator.constraints, ale wygląda na to, że nie używasz Hibernate.

Alternatywnie, jeśli zaimplementujesz walidator Springa, utworzysz klasę w następujący sposób:

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]");
        }
    }
}

Jeśli używasz powyższego walidatora, musisz również powiązać walidator ze sterownikiem sprężyny (nie jest to konieczne, jeśli używasz adnotacji):

@InitBinder("foo")
protected void initBinder(WebDataBinder binder) {
    binder.setValidator(new FooValidator());
}

Zobacz też Spring docs .

Mam nadzieję, że to pomoże.
 29
Author: stephen.hanson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-02-27 21:33:09

Chciałbym rozszerzyć miłą odpowiedź Jerome ' a Dalberta. Znalazłem bardzo łatwo napisać własne walidatory adnotacji w JSR-303 sposób. Nie ograniczasz się do walidacji "jedno pole". Możesz utworzyć własną adnotację na poziomie tekstu i mieć złożoną walidację(patrz przykłady poniżej). Wolę ten sposób, ponieważ nie potrzebuję mieszać różnych typów walidacji (Spring i JSR-303) jak Jerome. Również te walidatory są "świadome wiosny", więc możesz użyć @ Inject/ @ Autowire z box.

Przykład walidacji obiektu niestandardowego:

@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 {
}

Przykład równości pól generycznych:

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;

}
 12
Author: michal.kreuzman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-08-26 15:04:34

Jeśli masz taką samą logikę obsługi błędów dla różnych programów obsługi metod, to skończysz z wieloma programami obsługi z następującym wzorem kodu:

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

Załóżmy, że tworzysz usługi RESTful i chcesz zwrócić {[3] } wraz z komunikatami o błędach dla każdego przypadku błędu walidacji. Następnie część obsługi błędów będzie taka sama dla każdego punktu końcowego REST, który wymaga walidacji. Powtarzanie tej samej logiki w każdym handlerze nie jest takie suche ish!

Jeden sposobem na rozwiązanie tego problemu jest upuszczenie natychmiastowej BindingResult po każdym do walidacji. Twój opiekun będzie wyglądał tak:

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

W ten sposób, jeśli wiązana fasola nie była ważna, MethodArgumentNotValidException zostanie rzucona do wiosny. Można zdefiniować ControllerAdvice, który obsługuje ten wyjątek z tą samą logiką obsługi błędów:

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

Nadal można zbadać podstawowe BindingResult za pomocą getBindingResult metody MethodArgumentNotValidException.

 3
Author: Ali Dehghani,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-04-21 14:52:57

Znajdź pełny przykład walidacji 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);
    }
}
 1
Author: Vicky,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-11-15 21:32:00

Umieść tę fasolę w klasie konfiguracji.

 @Bean
  public Validator localValidatorFactoryBean() {
    return new LocalValidatorFactoryBean();
  }

I wtedy możesz użyć

 <T> BindingResult validate(T t) {
    DataBinder binder = new DataBinder(t);
    binder.setValidator(validator);
    binder.validate();
    return binder.getBindingResult();
}

Do ręcznego walidacji fasoli. Następnie otrzymasz wszystkie wyniki w BindingResult i możesz stamtąd pobrać.

 0
Author: praveen jain,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-02-15 14:02:36