Leitfaden für Spring Data REST-Validatoren

Leitfaden für Spring Data REST-Validatoren

1. Überblick

Dieser Artikel enthält eine grundlegende Einführung in Spring Data REST Validators. Wenn Sie zuerst die Grundlagen von Spring Data REST durchgehen müssen, besuchen Sie auf jeden Fallthis article, um die Grundlagen aufzufrischen.

Einfach gesagt, mit Spring Data REST können wir einfach einen neuen Eintrag in die Datenbank über die REST-API einfügen, aber wir müssen natürlich auch sicherstellen, dass die Daten gültig sind, bevor wir sie tatsächlich beibehalten.

Dieser Artikel wird aufexisting article fortgesetzt und wir werden das vorhandene Projekt, das wir dort eingerichtet haben, wiederverwenden.

 

Und wenn Sie auf die erstenget started with Spring Data REST schauen, ist hier ein guter Weg, um den ersten Schritt zu machen:

[.iframe-fluid] ##

2. Verwenden vonValidators

Ab Spring 3 verfügt das Framework über dieValidator-Schnittstelle, mit der Objekte validiert werden können.

2.1. Motivation

Im vorherigen Artikel haben wir unsere Entität mit zwei Eigenschaften definiert -name undemail.

Um eine neue Ressource zu erstellen, müssen wir einfach Folgendes ausführen:

curl -i -X POST -H "Content-Type:application/json" -d
  '{ "name" : "Test", "email" : "[email protected]" }'
  http://localhost:8080/users

Diese POST-Anforderung speichert das bereitgestellte JSON-Objekt in unserer Datenbank und die Operation gibt Folgendes zurück:

{
  "name" : "Test",
  "email" : "[email protected]",
  "_links" : {
    "self" : {
        "href" : "http://localhost:8080/users/1"
    },
    "websiteUser" : {
        "href" : "http://localhost:8080/users/1"
    }
  }
}

Ein positives Ergebnis wurde erwartet, da wir gültige Daten zur Verfügung stellten. Aber was passiert, wenn wir die Eigenschaftname entfernen oder den Wert einfach auf ein leeresString setzen?

Um das erste Szenario zu testen, führen wir einen geänderten Befehl aus, in dem wir eine leere Zeichenfolge als Wert für die Eigenschaftname festlegen:

curl -i -X POST -H "Content-Type:application/json" -d
  '{ "name" : "", "email" : "Baggins" }' http://localhost:8080/users

Mit diesem Befehl erhalten wir die folgende Antwort:

{
  "name" : "",
  "email" : "Baggins",
  "_links" : {
    "self" : {
        "href" : "http://localhost:8080/users/1"
    },
    "websiteUser" : {
        "href" : "http://localhost:8080/users/1"
    }
  }
}

Für das zweite Szenario entfernen wir die Eigenschaftname aus der Anforderung:

curl -i -X POST -H "Content-Type:application/json" -d
  '{ "email" : "Baggins" }' http://localhost:8080/users

Für diesen Befehl erhalten wir folgende Antwort:

{
  "name" : null,
  "email" : "Baggins",
  "_links" : {
    "self" : {
        "href" : "http://localhost:8080/users/2"
    },
    "websiteUser" : {
        "href" : "http://localhost:8080/users/2"
    }
  }
}

Wie wir sehen können, waren beide Anfragen in Ordnung und wir können dies mit 201 Statuscode und API-Link zu unserem Objekt. bestätigen

Dieses Verhalten ist nicht akzeptabel, da das Einfügen von Teildaten in eine Datenbank vermieden werden soll.

2.2. Spring Data REST-Ereignisse

Bei jedem Aufruf der Spring Data REST-API generiert der Spring Data REST-Exporter verschiedene Ereignisse, die hier aufgelistet sind:

  • BeforeCreateEvent

  • AfterCreateEvent

  • BeforeSaveEvent

  • AfterSaveEvent

  • BeforeLinkSaveEvent

  • AfterLinkSaveEvent

  • BeforeDeleteEvent

  • AfterDeleteEvent

Da alle Ereignisse auf ähnliche Weise behandelt werden, wird nur gezeigt, wiebeforeCreateEvent behandelt werden, die generiert werden, bevor ein neues Objekt in der Datenbank gespeichert wird.

2.3. Validator definieren

Um unseren eigenen Validator zu erstellen, müssen wir die Schnittstelleorg.springframework.validation.Validator mit den Methodensupports undvalidate implementieren.

Supports prüft, ob der Validator bereitgestellte Anforderungen unterstützt, während die Methode vonvalidatebereitgestellte Daten in Anforderungen überprüft.

Definieren wir die KlasseWebsiteUserValidator:

public class WebsiteUserValidator implements Validator {

    @Override
    public boolean supports(Class clazz) {
        return WebsiteUser.class.equals(clazz);
    }

    @Override
    public void validate(Object obj, Errors errors) {
        WebsiteUser user = (WebsiteUser) obj;
        if (checkInputString(user.getName())) {
            errors.rejectValue("name", "name.empty");
        }

        if (checkInputString(user.getEmail())) {
            errors.rejectValue("email", "email.empty");
        }
    }

    private boolean checkInputString(String input) {
        return (input == null || input.trim().length() == 0);
    }
}

Das ObjektErrorsist eine spezielle Klasse, die alle in der Methodevalidateangegebenen Fehler enthält. Später in diesem Artikel zeigen wir, wie Sie bereitgestellte Nachrichten verwenden können, die im Objekt vonErrorsenthalten sind. Um eine neue Fehlermeldung hinzuzufügen, müssen wirerrors.rejectValue(nameOfField, errorMessage) aufrufen.

Nachdem wir den Validator definiert haben, müssen wir ihn einem bestimmten Ereignis zuordnen, das generiert wird, nachdem die Anforderung akzeptiert wurde.

In unserem Fall wird beispielsweisebeforeCreateEvent generiert, weil wir ein neues Objekt in unsere Datenbank einfügen möchten. Da wir jedoch ein Objekt in einer Anfrage validieren möchten, müssen wir zuerst unseren Validator definieren.

Dies kann auf drei Arten erfolgen:

  • Fügen Sie die Anmerkung vonComponent mit dem Namen „beforeCreateWebsiteUserValidator“ hinzu. Spring Boot erkennt das PräfixbeforeCreate, das das Ereignis bestimmt, das wir abfangen möchten, und erkennt auch die Klasse vonWebsiteUseranhand des Namens vonComponent.

    @Component("beforeCreateWebsiteUserValidator")
    public class WebsiteUserValidator implements Validator {
        ...
    }
  • Erstellen SieBean im Anwendungskontext mit der Anmerkung von@Bean:

    @Bean
    public WebsiteUserValidator beforeCreateWebsiteUserValidator() {
        return new WebsiteUserValidator();
    }
  • Manuelle Registrierung:

    @SpringBootApplication
    public class SpringDataRestApplication
      extends RepositoryRestConfigurerAdapter {
        public static void main(String[] args) {
            SpringApplication.run(SpringDataRestApplication.class, args);
        }
    
        @Override
        public void configureValidatingRepositoryEventListener(
          ValidatingRepositoryEventListener v) {
            v.addValidator("beforeCreate", new WebsiteUserValidator());
        }
    }
    • In diesem Fall benötigen Sie keine Anmerkungen zur KlasseWebsiteUserValidator.

2.4. Ereigniserkennungsfehler

Im Moment abug exists in Spring Data REST - was die Ereigniserkennung beeinflusst.

Wenn wir eine POST-Anforderung aufrufen, die das EreignisbeforeCreategeneriert, ruft unsere Anwendung den Validator nicht auf, da das Ereignis aufgrund dieses Fehlers nicht erkannt wird.

Eine einfache Problemumgehung für dieses Problem besteht darin, alle Ereignisse in die Klasse von Spring Data RESTValidatingRepositoryEventListenereinzufügen:

@Configuration
public class ValidatorEventRegister implements InitializingBean {

    @Autowired
    ValidatingRepositoryEventListener validatingRepositoryEventListener;

    @Autowired
    private Map validators;

    @Override
    public void afterPropertiesSet() throws Exception {
        List events = Arrays.asList("beforeCreate");
        for (Map.Entry entry : validators.entrySet()) {
            events.stream()
              .filter(p -> entry.getKey().startsWith(p))
              .findFirst()
              .ifPresent(
                p -> validatingRepositoryEventListener
               .addValidator(p, entry.getValue()));
        }
    }
}

3. Testen

InSection 2.1. haben wir gezeigt, dass wir ohne Validator Objekte ohne Namenseigenschaft zu unserer Datenbank hinzufügen können. Dies ist kein gewünschtes Verhalten, da wir die Datenintegrität nicht überprüfen.

Wenn wir dasselbe Objekt ohne die Eigenschaftname, aber mit dem bereitgestellten Validator hinzufügen möchten, wird folgende Fehlermeldung angezeigt:

curl -i -X POST -H "Content-Type:application/json" -d
  '{ "email" : "[email protected]" }' http://localhost:8080/users
{
   "timestamp":1472510818701,
   "status":406,
   "error":"Not Acceptable",
   "exception":"org.springframework.data.rest.core.
    RepositoryConstraintViolationException",
   "message":"Validation failed",
   "path":"/users"
}

Wie wir sehen können, wurden fehlende Daten aus der Anfrage erkannt und ein Objekt wurde nicht in der Datenbank gespeichert. Unsere Anfrage wurde mit 500 HTTP-Code und Nachricht für einen internen Fehler zurückgegeben.

Die Fehlermeldung sagt nichts über das Problem in unserer Anfrage aus. Wenn wir es informativer gestalten möchten, müssen wir das Antwortobjekt ändern.

InException Handling in Spring article haben wir gezeigt, wie mit vom Framework generierten Ausnahmen umgegangen wird. Dies ist an dieser Stelle definitiv eine gute Lektüre.

Da unsere Anwendung eineRepositoryConstraintViolationException-Ausnahme generiert, erstellen wir einen Handler für diese bestimmte Ausnahme, der die Antwortnachricht ändert.

Dies ist unsereRestResponseEntityExceptionHandler-Klasse:

@ControllerAdvice
public class RestResponseEntityExceptionHandler extends
  ResponseEntityExceptionHandler {

    @ExceptionHandler({ RepositoryConstraintViolationException.class })
    public ResponseEntity handleAccessDeniedException(
      Exception ex, WebRequest request) {
          RepositoryConstraintViolationException nevEx =
            (RepositoryConstraintViolationException) ex;

          String errors = nevEx.getErrors().getAllErrors().stream()
            .map(p -> p.toString()).collect(Collectors.joining("\n"));

          return new ResponseEntity(errors, new HttpHeaders(),
            HttpStatus.PARTIAL_CONTENT);
    }
}


Mit diesem benutzerdefinierten Handler verfügt unser Rückgabeobjekt über Informationen zu allen erkannten Fehlern.

4. Fazit

In diesem Artikel haben wir gezeigt, dass Validatoren für jede Spring Data REST-API unerlässlich sind, die eine zusätzliche Sicherheitsebene für das Einfügen von Daten bietet.

Wir haben auch gezeigt, wie einfach es ist, einen neuen Validator mit Anmerkungen zu erstellen.

Der Code für diese Anwendung befindet sich wie immer inGitHub project.