it-swarm.com.de

Spring MVC: Wie führt man eine Validierung durch?

Ich würde gerne wissen, was die sauberste und beste Möglichkeit ist, die Formularvalidierung von Benutzereingaben durchzuführen. Ich habe einige Entwickler gesehen, die org.springframework.validation.Validator implementieren. Eine Frage dazu: Ich habe gesehen, dass es eine Klasse bestätigt. Muss die Klasse manuell mit den Werten aus der Benutzereingabe gefüllt und dann an den Prüfer übergeben werden?

Ich bin verwirrt über die sauberste und beste Möglichkeit, die Benutzereingaben zu überprüfen. Ich kenne die traditionelle Methode, request.getParameter() zu verwenden und dann manuell nach nulls zu suchen, aber ich möchte nicht alle Validierungen in meiner Controller durchführen. Einige gute Ratschläge in diesem Bereich werden sehr geschätzt. Ich verwende Hibernate nicht in dieser Anwendung.

141
devdar

Mit Spring MVC gibt es drei Möglichkeiten, die Validierung durchzuführen: Verwenden von Anmerkungen, manuell oder eine Kombination aus beiden. Es gibt keinen eindeutigen "saubersten und besten Weg", um zu überprüfen, aber es gibt wahrscheinlich einen, der besser zu Ihrem Projekt/Problem/Kontext passt.

Lass uns einen Benutzer haben:

public class User {

    private String name;

    ...

}

Methode 1: Wenn Sie Spring 3.x + und eine einfache Validierung verwenden möchten, verwenden Sie javax.validation.constraints-Anmerkungen (auch als JSR-303-Anmerkungen bezeichnet).

public class User {

    @NotNull
    private String name;

    ...

}

Sie benötigen einen JSR-303-Provider in Ihren Bibliotheken, z. B. Hibernate Validator , der die Referenzimplementierung darstellt (diese Bibliothek hat nichts mit Datenbanken und relationalem Mapping zu tun. Sie führt lediglich eine Validierung durch :-).

Dann hätten Sie in Ihrem Controller so etwas wie:

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

Beachten Sie das @Valid: Wenn der Benutzer einen Nullnamen hat, ist result.hasErrors () wahr.

Methode 2: Wenn Sie über eine komplexe Validierung verfügen (z. B. Validierungslogik für große Unternehmen, bedingte Validierung über mehrere Felder hinweg usw.) oder aus irgendeinem Grund Methode 1 nicht verwenden können, verwenden Sie die manuelle Validierung. Es empfiehlt sich, den Code des Controllers von der Validierungslogik zu trennen. Erstellen Sie Ihre Validierungsklasse (n) nicht von Grund auf neu. Spring bietet eine praktische org.springframework.validation.Validator-Schnittstelle (seit Spring 2).

Nehmen wir also an, Sie haben es getan

public class User {

    private String name;

    private Integer birthYear;
    private User responsibleUser;
    ...

}

und Sie möchten eine "komplexe" Validierung durchführen, z. B. wenn das Alter des Benutzers unter 18 Jahren liegt und der verantwortliche Benutzer nicht null sein darf und das Alter des verantwortlichen Benutzers über 21 Jahre alt sein muss.

Sie werden so etwas tun

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

    }

}

Dann hätten Sie in Ihrem Controller:

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

Bei Validierungsfehlern ist result.hasErrors () wahr.

Hinweis: Sie können den Validator auch in einer @InitBinder-Methode des Controllers mit "binder.setValidator (...)" festlegen (in diesem Fall wäre eine Verwendung der Methoden 1 und 2 nicht möglich, da Sie die Standardeinstellung ersetzen.) Validator). Oder Sie können es im Standardkonstruktor des Controllers instanziieren. Oder Sie haben einen @ Component/@ Service UserValidator, den Sie in Ihren Controller einführen (@Autowired): Sehr nützlich, da die meisten Validatoren aus Singletons bestehen + Einheitstest-Mocking wird einfacher + Ihr Validator könnte andere Spring-Komponenten aufrufen.

Methode 3: Warum keine Kombination beider Methoden verwenden? Überprüfen Sie die einfachen Daten wie das Attribut "name" mit Anmerkungen (dies ist schnell, prägnant und lesbarer). Behalten Sie die umfangreichen Validierungen für Validatoren bei (wenn es Stunden dauern würde, benutzerdefinierte, komplexe Validierungsanmerkungen zu codieren, oder nur, wenn es nicht möglich ist, Annotationen zu verwenden). Ich habe das bei einem früheren Projekt gemacht, es hat wie ein Zauber funktioniert, schnell und einfach.

Warnung: Sie dürfen validation handling nicht mit exception handling verwechseln. Lies diesen Post um zu wissen, wann man sie benutzt.

Verweise :

307
Jerome Dalbert

Es gibt zwei Möglichkeiten, Benutzereingaben zu überprüfen: Anmerkungen und durch Vererbung der Validator-Klasse von Spring. In einfachen Fällen sind die Anmerkungen Nizza. Wenn Sie komplexe Validierungen benötigen (z. B. Cross-Field-Validierung, z. B. Feld "E-Mail-Adresse bestätigen"), oder wenn Ihr Modell an mehreren Stellen in Ihrer Anwendung mit unterschiedlichen Regeln validiert wird oder wenn Sie keine Möglichkeit haben, Ihr zu ändern Wenn Sie ein Modellobjekt mit Anmerkungen versehen, ist der vererbungsbasierte Validator von Spring der richtige Weg. Ich werde Beispiele für beide zeigen.

Der tatsächliche Validierungsteil ist derselbe, unabhängig davon, welche Art von Validierung Sie verwenden:

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

Wenn Sie Annotationen verwenden, könnte Ihre Foo-Klasse folgendermaßen aussehen:

public class Foo {

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

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

    // getters, setters
}

Die obigen Anmerkungen sind javax.validation.constraints-Anmerkungen. Sie können auch org.hibernate.validator.constraints von Hibernate verwenden, aber es sieht nicht so aus, als würden Sie Hibernate verwenden. 

Wenn Sie den Validator von Spring implementieren, erstellen Sie eine Klasse alternativ wie folgt:

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

Wenn Sie den obigen Validator verwenden, müssen Sie den Validator auch an den Spring-Controller binden (nicht erforderlich, wenn Sie Anmerkungen verwenden):

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

Siehe auch Spring docs .

Hoffentlich hilft das.

29
stephen.hanson

Ich möchte gerne die schöne Antwort von Jerome Dalbert ausdrücken. Ich fand es sehr einfach, Ihre eigenen Anmerkungsprüfer auf JSR-303-Art zu schreiben. Sie sind nicht darauf beschränkt, eine Prüfung mit einem Feld zu haben. Sie können Ihre eigene Annotation auf Typebene erstellen und eine komplexe Validierung durchführen (siehe Beispiele unten). Ich bevorzuge diesen Weg, da ich keine verschiedenen Validierungsarten (Spring und JSR-303) wie Jerome mischen muss. Auch diese Validatoren sind "spring-aware", sodass Sie @ Inject/@ Autowire sofort verwenden können.

Beispiel für die Überprüfung benutzerdefinierter Objekte:

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

Beispiel für generische Felder Gleichheit:

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
michal.kreuzman

Wenn Sie dieselbe Fehlerbehandlungslogik für verschiedene Methodenhandler haben, erhalten Sie am Ende viele Handler mit folgendem Codemuster:

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

Angenommen, Sie erstellen RESTful-Services und möchten für jeden Validierungsfehlerfall 400 Bad Request zusammen mit Fehlernachrichten zurückgeben. Dann wäre der Fehlerbehandlungsteil für jeden einzelnen REST -Endpunkt, für den eine Überprüfung erforderlich ist, derselbe. Dieselbe Logik in jedem einzelnen Handler zu wiederholen, ist nicht soDRYish!

Eine Möglichkeit, dieses Problem zu lösen, besteht darin, die unmittelbare BindingResult nach jeder To-Be-Validated - Bean abzulegen. Nun wäre Ihr Handler so:

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

Wenn die gebundene Bean nicht gültig ist, wird auf diese Weise eine MethodArgumentNotValidException von Spring ausgelöst. Sie können eine ControllerAdvice definieren, die diese Ausnahme mit derselben Fehlerbehandlungslogik behandelt:

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

Sie können die zugrunde liegende BindingResult noch mit der getBindingResult-Methode von MethodArgumentNotValidException untersuchen.

3
Ali Dehghani

Hier finden Sie ein vollständiges Beispiel für Spring Mvc Validation

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
Vicky

Fügen Sie diese Bean in Ihre Konfigurationsklasse ein. 

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

und dann können Sie verwenden 

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

für die manuelle Validierung einer Bean. Sie erhalten dann alle Ergebnisse in BindingResult und können von dort abrufen. 

0
praveen jain